Compare commits

..

90 Commits

Author SHA1 Message Date
Nicolas Gallagher
023ca27a22 0.0.58 2016-12-14 17:39:39 +00:00
Nicolas Gallagher
80ba119b83 Update install command
Close #285
Close #286
2016-12-14 17:05:15 +00:00
Nicolas Gallagher
c30b67f6db 0.0.57 2016-12-12 15:10:39 +00:00
Nicolas Gallagher
4580f93199 Use fbjs requestAnimationFrame in Image 2016-12-12 14:26:07 +00:00
Nicolas Gallagher
4c46126ffe [change] ScrollView event normalization 2016-12-12 14:21:33 +00:00
Calvin Chan
f8d5c15405 [add] Button component 2016-12-12 13:02:16 +00:00
Nicolas Gallagher
dc54e03625 [add] Linking API
Adds support for opening external URLs in a new tab/window. Includes
patches to 'Text' to improve accessibility and 'createDOMElement' to
improve external link security.

Fix #198
2016-12-12 11:45:30 +00:00
Nicolas Gallagher
4d5819ae28 [fix] RTL translateX; Switch transition 2016-12-08 19:40:34 -08:00
Nicolas Gallagher
5c482ef3be 0.0.56 2016-12-08 18:29:38 -08:00
Nicolas Gallagher
f51592f96e [change] TouchableOpacity without Animated
Fix #259
2016-12-08 18:22:25 -08:00
Nicolas Gallagher
6bffe1775f Fix lint error 2016-12-07 16:49:53 -08:00
Nicolas Gallagher
7e75d037f2 [fix] Image passes unknown props to underlying View
Fix #267
2016-12-07 16:37:24 -08:00
Nicolas Gallagher
7536508fe3 Update docs 2016-12-07 16:22:39 -08:00
Maxime Thirouin
945fff0015 Add source files to published package 2016-11-25 12:38:29 -08:00
Nicolas Gallagher
5032ed6fe1 Update AppRegistry docs 2016-11-25 12:36:05 -08:00
Nicolas Gallagher
8c7cdbf4be 0.0.55 2016-11-24 10:24:19 -08:00
Nicolas Gallagher
e5d8857bcc [fix] inject ReactDefaultInjection
Fixes a regression introduced in the following commit to avoid directly
depending on the 'react-dom' entry file:

d65c92eea9

Injecting ReactDefaultInjection adds ~25 KB back to the UMD build.

Fix #263
2016-11-24 10:21:40 -08:00
Nicolas Gallagher
cda8d05bb7 0.0.54 2016-11-24 08:42:53 -08:00
Nicolas Gallagher
049edc4611 [change] don't prefix HTML id's with underscore 2016-11-23 10:28:20 -08:00
Nicolas Gallagher
e76d5a4e6c [change] export createDOMElement helper
Fix #184
2016-11-23 10:25:00 -08:00
Nicolas Gallagher
f71dae7d93 [add] support for vendor-prefixed font-smoothing styles
Fix #240
2016-11-23 09:58:18 -08:00
Nicolas Gallagher
94d31beaf4 [change] ignore unsupported React Native props
Ignores RN props that RN packages commonly applied to elements without
scoping them to supported platforms.

Fix #252
2016-11-23 09:27:27 -08:00
Nicolas Gallagher
f5f9389728 0.0.53 2016-11-22 17:46:57 -08:00
Nicolas Gallagher
fdbd19a4af [fix] PropTypes production error
See: https://github.com/oliviertassinari/babel-plugin-transform-react-remove-prop-types/issues/68
2016-11-22 17:44:48 -08:00
Nicolas Gallagher
36eafbc2f5 0.0.52 2016-11-22 17:10:14 -08:00
Nicolas Gallagher
bca3398c1c Correct devDependencies 2016-11-22 17:08:04 -08:00
Nicolas Gallagher
722d77e8e5 Smaller production builds
Builds on the exclusion of PropTypes from production builds:

- Remove 'lodash' and use smaller modules for equivalent functions.
- Remove use of some unnecessary Facebook utilities.
- Remove 'TouchableBounce'; it isn't part of React Native anymore.
- Remove stray import of 'react-dom/server'.
- Exclude 'StyleSheetValidation' from production.

Measuring the UMD build (gzip)…

Before: ~100KB
After: ~60KB
2016-11-22 16:59:20 -08:00
Nicolas Gallagher
d65c92eea9 [change] prepare for react-dom@15.4.0
Don't depend directly on the 'react-dom' module as it will be prebuilt
in 15.4. Leave server-side rendering to 'react-dom/server'.
2016-11-22 16:57:28 -08:00
Nicolas Gallagher
8dd39c681c [change] allow propTypes to be removed in production builds
Fix #254
2016-11-22 16:57:22 -08:00
Nicolas Gallagher
0b1759363d [add] promote ScrollView content to new layer 2016-11-22 13:08:11 -08:00
Nicolas Gallagher
abd1227a94 [change] ScrollView props and event handling
- Update 'scrollEventThrottle' prop
- Filter non-DOM props
- Persist debounced scroll events.

Fix #209
2016-11-21 21:39:08 -08:00
Nicolas Gallagher
8352c7cbda Use yarn for dependency management 2016-11-21 17:10:50 -08:00
Nicolas Gallagher
89f5a13891 [change] TextInput uses DOM elements
This patch changes TextInput to use DOM inputs directly, rather than
trying to reimplement 'placeholder'. Removes support for
'placeholderTextColor'.

Fix #54
Fix #224
Fix #229
Fix #235
Fix #253
2016-11-21 16:52:40 -08:00
Nicolas Gallagher
4005f9ddde 0.0.51 2016-11-21 12:42:58 -08:00
Nicolas Gallagher
f192a9ba26 [fix] Depend on React@15.3
React@15.4 includes changes that prevent the ResponderEventPlugin from
being properly injected, which breaks Touchables and PanResponder.

Fix #257
2016-11-21 12:38:37 -08:00
Nicolas Gallagher
e688a949fb 0.0.50 2016-11-20 14:00:42 -08:00
Nicolas Gallagher
77605cb27c [add] Text accessibility roles
Fix #199
2016-11-20 13:56:49 -08:00
Nicolas Gallagher
4f71833aec [fix] Image rendering in Safari
The use of 'max-height:100%' on the inner image can cause extremely poor
render performance in Safari. Remove the inner image and simplify
`Image` to use a single view. This fixes the following additional bugs:

Fix #202
Fix #226
2016-11-20 13:51:16 -08:00
Nicolas Gallagher
fa14995a35 Use jest for testing
Thanks to @paularmstrong:
https://github.com/necolas/react-native-web/pull/249
2016-11-09 10:00:49 -08:00
Paul Armstrong
4beae0dd78 [fix] NetInfo event handler registration 2016-11-04 10:20:19 -07:00
Nicolas Gallagher
5598961d2c Move ResponderEventPlugin injection to View 2016-11-04 10:09:55 -07:00
Nicolas Gallagher
4613baf0e8 [fix] StyleSheet check 'transform' is an array 2016-11-04 10:09:27 -07:00
Nicolas Gallagher
3b661d8d6d 0.0.49 2016-11-03 09:16:00 -07:00
Nicolas Gallagher
22d20706e3 Add React Native TextInput examples 2016-11-03 08:56:26 -07:00
Nicolas Gallagher
0b2813b186 [fix] View when 'style' is not defined 2016-11-03 08:52:25 -07:00
Nicolas Gallagher
b248de552d Fix tests 2016-11-03 08:51:51 -07:00
Nicolas Gallagher
2b826dc7f4 [add] TextInput support for selection 2016-10-28 23:37:19 -07:00
Nicolas Gallagher
b46acd4f50 [fix] TextInputState focus management 2016-10-28 22:12:20 -07:00
Nicolas Gallagher
5a03cb25cb [add] TextInput support for blurOnSubmit and onSubmitEditing 2016-10-28 21:15:35 -07:00
Nicolas Gallagher
44e60d12e3 [change] TextInput support for autoCorrect and autoComplete 2016-10-28 10:51:05 -07:00
Nicolas Gallagher
fc60f8d332 [add] TextInput support for autoCapitalize 2016-10-28 10:36:06 -07:00
Nicolas Gallagher
2a65ca6fc0 [add] TextInput support for isFocused 2016-10-27 22:31:43 -07:00
Nicolas Gallagher
9db3bd7e67 [add] TextInput support for onKeyPress
Fix #215
2016-10-27 22:17:59 -07:00
Nicolas Gallagher
1963e9109a 0.0.48 2016-10-27 21:12:04 -07:00
Nicolas Gallagher
14072c7471 [fix] View event handling
Fix #238
2016-10-27 21:00:17 -07:00
Nicolas Gallagher
0af6dc00fc [change] Image 'source' dimensions and RN layout
Adds support for 'width' and 'height' set via the 'source' property.
Emulates RN image layout (i.e., no dimensions by default).

Fix #10
2016-10-23 19:19:52 -07:00
Nicolas Gallagher
c9d401f09a [fix] Image resizeMode style
Fixes an issue in production where 'resizeMode' is deleted from style
simple objects, preventing it from being applied at render time.

Fix #233
2016-10-23 14:50:25 -07:00
Nicolas Gallagher
8aeeed0ef7 [fix] accept number or string for flexBasis style
Fix #230
2016-10-20 10:36:40 -07:00
Nicolas Gallagher
f5d0f73b4f 0.0.47 2016-10-13 10:30:49 -07:00
Xiaohan Zhang
ee7d367062 [fix] AsyncStorage.mergeItem to support deep merge
Mirrors behaviour of react-native
2016-10-13 10:27:51 -07:00
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
164 changed files with 13349 additions and 2968 deletions

View File

@@ -1,5 +1,8 @@
{
"presets": [
"react-native"
],
"plugins": [
[ "transform-react-remove-prop-types", { "mode": "wrap" } ]
]
}

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

View File

@@ -5,5 +5,4 @@ before_script:
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
script:
- npm run lint
- npm test

View File

@@ -19,6 +19,12 @@ Fork, then clone the repo:
git clone https://github.com/your-username/react-native-web.git
```
Install dependencies (requires [yarn](https://yarnpkg.com/en/docs/install):
```
yarn
```
Run the examples:
```
@@ -51,7 +57,7 @@ To continuously watch and run tests, run the following:
npm run test:watch
```
To perform linting, run the following:
To perform only linting, run the following:
```
npm run lint

143
README.md
View File

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

View File

@@ -3,8 +3,7 @@
`AppRegistry` is the control point for registering, running, prerendering, and
unmounting all apps. App root components should register themselves with
`AppRegistry.registerComponent`. Apps can be run by invoking
`AppRegistry.runApplication`, and prerendered by invoking
`AppRegistry.prerenderApplication` (see the [client and server rendering
`AppRegistry.runApplication` (see the [client and server rendering
guide](../guides/rendering.md) for more details).
To "stop" an application when a view should be destroyed, call
@@ -13,14 +12,11 @@ into `runApplication`. These should always be used as a pair.
## Methods
(web) static **prerenderApplication**(appKey:string, appParameters: object)
(web) static **getApplication**(appKey:string, appParameters: object)
Renders the given application to an HTML string. Use this for server-side
rendering. Return object is of type `{ html: string; style: string;
styleElement: ReactComponent }`. `html` is the prerendered HTML, `style` is the
prerendered style sheet, and `styleElement` is a React Component. It's
recommended that you use `styleElement` to render the style sheet in an app
shell.
Returns the given application element. Use this for server-side rendering.
Return object is of type `{ element: ReactElement; stylesheet: ReactElement }`.
It's recommended that you use `sheetsheet` to render the style sheet in an app
static **registerConfig**(config: Array<AppConfig>)

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`.

39
docs/components/Button.md Normal file
View File

@@ -0,0 +1,39 @@
# Button
A basic button component. Supports a minimal level of customization. You can
build your own custom button using `TouchableOpacity` or
`TouchableNativeFeedback`.
## Props
**accessibilityLabel**: string
Defines the text available to assistive technologies upon interaction with the
element. (This is implemented using `aria-label`.)
**color**: string
Background color of the button.
**disabled**: bool = false
If true, disable all interactions for this component
**onPress**: function
This function is called on press.
**title**: string
Text to display inside the button.
## Examples
```js
<Button
accessibilityLabel="Learn more about this purple button"
color="#841584"
onPress={onPressLearnMore}
title="Learn More"
/>
```

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.

View File

@@ -38,6 +38,18 @@ which this `ScrollView` renders.
Fires at most once per frame during scrolling. The frequency of the events can
be contolled using the `scrollEventThrottle` prop.
Invoked on scroll with the following event:
```js
{
nativeEvent: {
contentOffset: { x, y },
contentSize: { height, width },
layoutMeasurement: { height, width }
}
}
```
**refreshControl**: element
TODO
@@ -51,8 +63,8 @@ When false, the content does not scroll.
**scrollEventThrottle**: number = 0
This controls how often the scroll event will be fired while scrolling (in
events per seconds). A higher number yields better accuracy for code that is
This controls how often the scroll event will be fired while scrolling (as a
time interval in ms). A lower number yields better accuracy for code that is
tracking the scroll position, but can lead to scroll performance problems. The
default value is `0`, which means the scroll event will be sent only once each
time the view is scrolled.
@@ -104,7 +116,7 @@ export default class ScrollViewExample extends Component {
contentContainerStyle={styles.container}
horizontal
onScroll={(e) => this.onScroll(e)}
scrollEventThrottle={60}
scrollEventThrottle={100}
style={styles.root}
/>
)

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

@@ -6,12 +6,10 @@ such as auto-complete, auto-focus, placeholder text, and event callbacks.
Note: some props are exclusive to or excluded from `multiline`.
Unsupported React Native props:
`autoCapitalize`,
`autoCorrect`,
`onEndEditing`,
`onSubmitEditing`,
`clearButtonMode` (ios),
`enablesReturnKeyAutomatically` (ios),
`placeholderTextColor`,
`returnKeyType` (ios),
`selectionState` (ios),
`underlineColorAndroid` (android)
@@ -20,15 +18,37 @@ Unsupported React Native props:
[...View props](./View.md)
(web) **autoComplete**: bool = false
**autoCapitalize**: oneOf('characters', 'none', 'sentences', 'words') = 'sentences'
Indicates whether the value of the control can be automatically completed by the browser.
Automatically capitalize certain characters (only available in Chrome and iOS Safari).
* `characters`: Automatically capitalize all characters.
* `none`: Completely disables automatic capitalization
* `sentences`: Automatically capitalize the first letter of sentences.
* `words`: Automatically capitalize the first letter of words.
(web) **autoComplete**: string
Indicates whether the value of the control can be automatically completed by
the browser. [Accepted values](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input).
**autoCorrect**: bool = true
Automatically correct spelling mistakes (only available in iOS Safari).
**autoFocus**: bool = false
If true, focuses the input on `componentDidMount`. Only the first form element
If `true`, focuses the input on `componentDidMount`. Only the first form element
in a document with `autofocus` is focused.
**blurOnSubmit**: bool
If `true`, the text field will blur when submitted. The default value is `true`
for single-line fields and `false` for multiline fields. Note, for multiline
fields setting `blurOnSubmit` to `true` means that pressing return will blur
the field and trigger the `onSubmitEditing` event instead of inserting a
newline into the field.
**clearTextOnFocus**: bool = false
If `true`, clears the text field automatically when focused.
@@ -87,29 +107,25 @@ as an argument to the callback handler.
Callback that is called when the text input is focused.
(web) **onSelectionChange**: function
**onKeyPress**: function
Callback that is called when the text input's selection changes. The following
object is passed as an argument to the callback handler.
Callback that is called when a key is pressed. Pressed key value is passed as
an argument to the callback handler. Fires before `onChange` callbacks.
```js
{
selectionDirection,
selectionEnd,
selectionStart,
nativeEvent
}
```
**onSelectionChange**: function
Callback that is called when the text input's selection changes. This will be called with
`{ nativeEvent: { selection: { start, end } } }`.
**onSubmitEditing**: function
Callback that is called when the keyboard's submit button is pressed.
**placeholder**: string
The string that will be rendered in an empty `TextInput` before text has been
entered.
**placeholderTextColor**: string
The text color of the placeholder string.
**secureTextEntry**: bool = false
If true, the text input obscures the text entered so that sensitive text like
@@ -117,6 +133,10 @@ passwords stay secure.
(Not available when `multiline` is `true`.)
**selection**: { start: number, end: ?number }
The start and end of the text input's selection. Set start and end to the same value to position the cursor.
**selectTextOnFocus**: bool = false
If `true`, all text will automatically be selected on focus.
@@ -152,6 +172,10 @@ Clear the text from the underlying DOM input.
Focus the underlying DOM input.
**isFocused()**
Returns `true` if the input is currently focused; `false` otherwise.
## Examples
```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

@@ -43,12 +43,7 @@ import ReactNative from 'react-native'
// component that renders the app
const AppHeaderContainer = (props) => { /* ... */ }
// DOM render
ReactNative.render(<AppHeaderContainer />, document.getElementById('react-app-header'))
// Server render
ReactNative.renderToString(<AppHeaderContainer />)
ReactNative.renderToStaticMarkup(<AppHeaderContainer />)
```
Rendering using the `AppRegistry`:
@@ -63,12 +58,27 @@ const AppContainer = (props) => { /* ... */ }
// register the app
AppRegistry.registerComponent('App', () => AppContainer)
// DOM render
AppRegistry.runApplication('App', {
initialProps: {},
rootTag: document.getElementById('react-app')
})
```
## Server-side rendering
Rendering using the `AppRegistry`:
```js
import ReactDOMServer from 'react-dom/server'
import ReactNative, { AppRegistry } from 'react-native'
// component that renders the app
const AppContainer = (props) => { /* ... */ }
// register the app
AppRegistry.registerComponent('App', () => AppContainer)
// prerender the app
const { html, styleElement } = AppRegistry.prerenderApplication('App', { initialProps })
const { element, stylesheet } = AppRegistry.getApplication('App', { initialProps });
const initialHTML = ReactDOMServer.renderToString(element);
```

View File

@@ -1,40 +0,0 @@
import React from 'react';
import { storiesOf, action } from '@kadira/storybook';
import { StyleSheet, TextInput, View } from 'react-native'
storiesOf('<TextInput>', module)
.add('tbd', () => (
<View>
<TextInput
defaultValue='Default textInput'
keyboardType='default'
onBlur={(e) => { console.log('TextInput.onBlur', e) }}
onChange={(e) => { console.log('TextInput.onChange', e) }}
onChangeText={(e) => { console.log('TextInput.onChangeText', e) }}
onFocus={(e) => { console.log('TextInput.onFocus', e) }}
onSelectionChange={(e) => { console.log('TextInput.onSelectionChange', e) }}
/>
<TextInput secureTextEntry style={styles.textInput} />
<TextInput defaultValue='read only' editable={false} style={styles.textInput} />
<TextInput
style={[ styles.textInput, { flex:1, height: 60, padding: 20, fontSize: 20, textAlign: 'center' } ]}
keyboardType='email-address' placeholder='you@domain.com' placeholderTextColor='red'
/>
<TextInput keyboardType='numeric' style={styles.textInput} />
<TextInput keyboardType='phone-pad' style={styles.textInput} />
<TextInput defaultValue='https://delete-me' keyboardType='url' placeholder='https://www.some-website.com' selectTextOnFocus style={styles.textInput} />
<TextInput
defaultValue='default value'
maxNumberOfLines={10}
multiline
numberOfLines={5}
style={styles.textInput}
/>
</View>
))
const styles = StyleSheet.create({
textInput: {
borderWidth: 1
}
})

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

@@ -0,0 +1,29 @@
import { Linking, StyleSheet, Text, View } from 'react-native'
import React, { Component } from 'react';
import { storiesOf, action } from '@kadira/storybook';
class LinkingExample extends Component {
render() {
return (
<View>
<Text onPress={() => { Linking.openURL('https://mathiasbynens.github.io/rel-noopener/malicious.html'); }} style={styles.text}>
Linking.openURL (Expect: "The previous tab is safe and intact")
</Text>
<Text accessibilityRole='link' href='https://mathiasbynens.github.io/rel-noopener/malicious.html' style={styles.text} target='_blank'>
target="_blank" (Expect: "The previous tab is safe and intact")
</Text>
</View>
);
}
}
const styles = StyleSheet.create({
text: {
marginVertical: 10
}
});
storiesOf('api: Linking', module)
.add('Safe linking', () => (
<LinkingExample />
));

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

@@ -0,0 +1,80 @@
import React from 'react';
import { action, storiesOf } from '@kadira/storybook';
import { Button, StyleSheet, View } from 'react-native';
const onButtonPress = action('Button has been pressed!');
const examples = [
{
title: 'Simple Button',
description: 'The title and onPress handler are required. It is ' +
'recommended to set accessibilityLabel to help make your app usable by ' +
'everyone.',
render: function() {
return (
<Button
onPress={onButtonPress}
title="Press Me"
accessibilityLabel="See an informative alert"
/>
);
},
},
{
title: 'Adjusted color',
description: 'Adjusts the color in a way that looks standard on each ' +
'platform. On iOS, the color prop controls the color of the text. On ' +
'Android, the color adjusts the background color of the button.',
render: function() {
return (
<Button
onPress={onButtonPress}
title="Press Purple"
color="#841584"
accessibilityLabel="Learn more about purple"
/>
);
},
},
{
title: 'Fit to text layout',
description: 'This layout strategy lets the title define the width of ' +
'the button',
render: function() {
return (
<View style={{flexDirection: 'row', justifyContent: 'space-between'}}>
<Button
onPress={onButtonPress}
title="This looks great!"
accessibilityLabel="This sounds great!"
/>
<Button
onPress={onButtonPress}
title="Ok!"
color="#841584"
accessibilityLabel="Ok, Great!"
/>
</View>
);
},
},
{
title: 'Disabled Button',
description: 'All interactions for the component are disabled.',
render: function() {
return (
<Button
disabled
onPress={onButtonPress}
title="I Am Disabled"
accessibilityLabel="See an informative alert"
/>
);
},
},
];
examples.forEach((example) => {
storiesOf('component: Button', module)
.add(example.title, () => example.render());
});

View File

@@ -218,7 +218,7 @@ const examples = [
render: function() {
return (
<Image
source={{uri: 'http://facebook.github.io/react/img/logo_og.png'}}
source={{ uri: 'http://facebook.github.io/react/img/logo_og.png', width: 1200, height: 630 }}
style={styles.base}
/>
);
@@ -651,7 +651,7 @@ var styles = StyleSheet.create({
});
examples.forEach((example) => {
storiesOf('<Image>', module)
.addDecorator((renderStory) => <View>{renderStory()}</View>)
storiesOf('component: Image', module)
.addDecorator((renderStory) => <View style={{ width: '100%' }}>{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

@@ -1,19 +1,21 @@
import React from 'react';
import { storiesOf, action } from '@kadira/storybook';
import { ScrollView, StyleSheet, Text, View } from 'react-native'
import { action, storiesOf } from '@kadira/storybook';
import { ScrollView, StyleSheet, Text, TouchableHighlight, View } from 'react-native'
storiesOf('<ScrollView>', module)
const onScroll = action('ScrollView.onScroll');
storiesOf('component: ScrollView', module)
.add('vertical', () => (
<View style={styles.scrollViewContainer}>
<ScrollView
contentContainerStyle={styles.scrollViewContentContainerStyle}
onScroll={e => console.log('ScrollView.onScroll', e)}
scrollEventThrottle={1} // 1 event per second
onScroll={onScroll}
scrollEventThrottle={1000} // 1 event per second
style={styles.scrollViewStyle}
>
{Array.from({ length: 50 }).map((item, i) => (
<View key={i} style={styles.box}>
<Text>{i}</Text>
<TouchableHighlight onPress={() => {}}><Text>{i}</Text></TouchableHighlight>
</View>
))}
</ScrollView>
@@ -24,8 +26,8 @@ storiesOf('<ScrollView>', module)
<ScrollView
contentContainerStyle={styles.scrollViewContentContainerStyle}
horizontal
onScroll={e => console.log('ScrollView.onScroll', e)}
scrollEventThrottle={1} // 1 event per second
onScroll={onScroll}
scrollEventThrottle={16} // ~60 events per second
style={styles.scrollViewStyle}
>
{Array.from({ length: 50 }).map((item, i) => (
@@ -39,7 +41,6 @@ storiesOf('<ScrollView>', module)
const styles = StyleSheet.create({
box: {
alignItems: 'center',
flexGrow: 1,
justifyContent: 'center',
borderWidth: 1
@@ -49,9 +50,10 @@ const styles = StyleSheet.create({
width: 300
},
scrollViewStyle: {
borderWidth: '1px'
borderWidth: 1
},
scrollViewContentContainerStyle: {
padding: '10px'
backgroundColor: '#eee',
padding: 10
}
})

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

@@ -79,7 +79,7 @@ const examples = [
title: 'Wrap',
render: function() {
return (
<Text>
<Text style={{ WebkitFontSmoothing: 'antialiased' }}>
The text should wrap if it goes on multiple lines. See, this is going to
the next line.
</Text>
@@ -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

@@ -0,0 +1,867 @@
import React from 'react';
import { storiesOf, action } from '@kadira/storybook';
import { StyleSheet, Text, TextInput, View } from 'react-native'
/**
* 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
*/
class WithLabel extends React.Component {
render() {
return (
<View style={styles.labelContainer}>
<View style={styles.label}>
<Text>{this.props.label}</Text>
</View>
{this.props.children}
</View>
);
}
}
class TextEventsExample extends React.Component {
state = {
curText: '<No Event>',
prevText: '<No Event>',
prev2Text: '<No Event>',
prev3Text: '<No Event>',
};
updateText = (text) => {
this.setState((state) => {
return {
curText: text,
prevText: state.curText,
prev2Text: state.prevText,
prev3Text: state.prev2Text,
};
});
};
render() {
return (
<View>
<TextInput
autoCapitalize="none"
placeholder="Enter text to see events"
autoCorrect={false}
onFocus={() => this.updateText('onFocus')}
onBlur={() => this.updateText('onBlur')}
onChange={(event) => this.updateText(
'onChange text: ' + event.nativeEvent.text
)}
onEndEditing={(event) => this.updateText(
'onEndEditing text: ' + event.nativeEvent.text
)}
onSubmitEditing={(event) => this.updateText(
'onSubmitEditing text: ' + event.nativeEvent.text
)}
onSelectionChange={(event) => this.updateText(
'onSelectionChange range: ' +
event.nativeEvent.selection.start + ',' +
event.nativeEvent.selection.end
)}
onKeyPress={(event) => {
this.updateText('onKeyPress key: ' + event.nativeEvent.key);
}}
style={styles.default}
/>
<Text style={styles.eventLabel}>
{this.state.curText}{'\n'}
(prev: {this.state.prevText}){'\n'}
(prev2: {this.state.prev2Text}){'\n'}
(prev3: {this.state.prev3Text})
</Text>
</View>
);
}
}
class AutoExpandingTextInput extends React.Component {
state: any;
constructor(props) {
super(props);
this.state = {
text: 'React Native enables you to build world-class application experiences on native platforms using a consistent developer experience based on JavaScript and React. The focus of React Native is on developer efficiency across all the platforms you care about — learn once, write anywhere. Facebook uses React Native in multiple production apps and will continue investing in React Native.',
height: 0,
};
}
render() {
return (
<TextInput
{...this.props}
multiline={true}
onChangeText={(text) => {
this.setState({text});
}}
onContentSizeChange={(event) => {
this.setState({height: event.nativeEvent.contentSize.height});
}}
style={[styles.default, {height: Math.max(35, this.state.height)}]}
value={this.state.text}
/>
);
}
}
class RewriteExample extends React.Component {
state: any;
constructor(props) {
super(props);
this.state = {text: ''};
}
render() {
var limit = 20;
var remainder = limit - this.state.text.length;
var remainderColor = remainder > 5 ? 'blue' : 'red';
return (
<View style={styles.rewriteContainer}>
<TextInput
multiline={false}
maxLength={limit}
onChangeText={(text) => {
text = text.replace(/ /g, '_');
this.setState({text});
}}
style={styles.default}
value={this.state.text}
/>
<Text style={[styles.remainder, {color: remainderColor}]}>
{remainder}
</Text>
</View>
);
}
}
class RewriteExampleInvalidCharacters extends React.Component {
state: any;
constructor(props) {
super(props);
this.state = {text: ''};
}
render() {
return (
<View style={styles.rewriteContainer}>
<TextInput
multiline={false}
onChangeText={(text) => {
this.setState({text: text.replace(/\s/g, '')});
}}
style={styles.default}
value={this.state.text}
/>
</View>
);
}
}
class TokenizedTextExample extends React.Component {
state: any;
constructor(props) {
super(props);
this.state = {text: 'Hello #World'};
}
render() {
//define delimiter
let delimiter = /\s+/;
//split string
let _text = this.state.text;
let token, index, parts = [];
while (_text) {
delimiter.lastIndex = 0;
token = delimiter.exec(_text);
if (token === null) {
break;
}
index = token.index;
if (token[0].length === 0) {
index = 1;
}
parts.push(_text.substr(0, index));
parts.push(token[0]);
index = index + token[0].length;
_text = _text.slice(index);
}
parts.push(_text);
return (
<View>
<TextInput
value={parts.join('')}
multiline={true}
style={styles.multiline}
onChangeText={(text) => {
this.setState({text});
}}
/>
</View>
);
}
}
class BlurOnSubmitExample extends React.Component {
focusNextField = (nextField) => {
this.refs[nextField].focus();
};
render() {
return (
<View>
<TextInput
ref="1"
style={styles.default}
placeholder="blurOnSubmit = false"
returnKeyType="next"
blurOnSubmit={false}
onSubmitEditing={() => this.focusNextField('2')}
/>
<TextInput
ref="2"
style={styles.default}
keyboardType="email-address"
placeholder="blurOnSubmit = false"
returnKeyType="next"
blurOnSubmit={false}
onSubmitEditing={() => this.focusNextField('3')}
/>
<TextInput
ref="3"
style={styles.default}
keyboardType="url"
placeholder="blurOnSubmit = false"
returnKeyType="next"
blurOnSubmit={false}
onSubmitEditing={() => this.focusNextField('4')}
/>
<TextInput
ref="4"
style={styles.default}
keyboardType="numeric"
placeholder="blurOnSubmit = false"
blurOnSubmit={false}
onSubmitEditing={() => this.focusNextField('5')}
/>
<TextInput
ref="5"
style={styles.default}
keyboardType="numeric"
placeholder="blurOnSubmit = true"
returnKeyType="done"
/>
</View>
);
}
}
type SelectionExampleState = {
selection: {
start: number;
end?: number;
};
value: string;
};
class SelectionExample extends React.Component {
state: SelectionExampleState;
_textInput: any;
constructor(props) {
super(props);
this.state = {
selection: {start: 0, end: 0},
value: props.value
};
}
onSelectionChange({nativeEvent: {selection}}) {
this.setState({selection});
}
getRandomPosition() {
var length = this.state.value.length;
return Math.round(Math.random() * length);
}
select(start, end) {
this._textInput.focus();
this.setState({selection: {start, end}});
}
selectRandom() {
var positions = [this.getRandomPosition(), this.getRandomPosition()].sort();
this.select(...positions);
}
placeAt(position) {
this.select(position, position);
}
placeAtRandom() {
this.placeAt(this.getRandomPosition());
}
render() {
var length = this.state.value.length;
return (
<View>
<TextInput
multiline={this.props.multiline}
onChangeText={(value) => this.setState({value})}
onSelectionChange={this.onSelectionChange.bind(this)}
ref={textInput => (this._textInput = textInput)}
selection={this.state.selection}
style={this.props.style}
value={this.state.value}
/>
<View>
<Text>
selection = {JSON.stringify(this.state.selection)}
</Text>
<Text onPress={this.placeAt.bind(this, 0)}>
Place at Start (0, 0)
</Text>
<Text onPress={this.placeAt.bind(this, length)}>
Place at End ({length}, {length})
</Text>
<Text onPress={this.placeAtRandom.bind(this)}>
Place at Random
</Text>
<Text onPress={this.select.bind(this, 0, length)}>
Select All
</Text>
<Text onPress={this.selectRandom.bind(this)}>
Select Random
</Text>
</View>
</View>
);
}
}
var styles = StyleSheet.create({
page: {
paddingBottom: 300,
},
default: {
height: 26,
borderWidth: 0.5,
borderColor: '#0f0f0f',
flex: 1,
fontSize: 13,
padding: 4,
},
multiline: {
borderWidth: 0.5,
borderColor: '#0f0f0f',
flex: 1,
fontSize: 13,
height: 50,
padding: 4,
marginBottom: 4,
},
multilineWithFontStyles: {
color: 'blue',
fontWeight: 'bold',
fontSize: 18,
fontFamily: 'Cochin',
height: 60,
},
multilineChild: {
width: 50,
height: 40,
position: 'absolute',
right: 5,
backgroundColor: 'red',
},
eventLabel: {
margin: 3,
fontSize: 12,
},
labelContainer: {
flexDirection: 'row',
marginVertical: 2,
flex: 1,
},
label: {
width: 115,
alignItems: 'flex-end',
marginRight: 10,
paddingTop: 2,
},
rewriteContainer: {
flexDirection: 'row',
alignItems: 'center',
},
remainder: {
textAlign: 'right',
width: 24,
},
hashtag: {
color: 'blue',
fontWeight: 'bold',
},
});
const examples = [
{
title: 'Auto-focus',
render: function() {
return (
<View>
<TextInput
autoFocus={true}
style={styles.default}
accessibilityLabel="I am the accessibility label for text input"
/>
</View>
);
}
},
{
title: "Live Re-Write (<sp> -> '_') + maxLength",
render: function() {
return <RewriteExample />;
}
},
{
title: 'Live Re-Write (no spaces allowed)',
render: function() {
return <RewriteExampleInvalidCharacters />;
}
},
{
title: 'Auto-capitalize',
render: function() {
return (
<View>
<WithLabel label="none">
<TextInput
autoCapitalize="none"
style={styles.default}
/>
</WithLabel>
<WithLabel label="sentences">
<TextInput
autoCapitalize="sentences"
style={styles.default}
/>
</WithLabel>
<WithLabel label="words">
<TextInput
autoCapitalize="words"
style={styles.default}
/>
</WithLabel>
<WithLabel label="characters">
<TextInput
autoCapitalize="characters"
style={styles.default}
/>
</WithLabel>
</View>
);
}
},
{
title: 'Auto-correct',
render: function() {
return (
<View>
<WithLabel label="true">
<TextInput autoCorrect={true} style={styles.default} />
</WithLabel>
<WithLabel label="false">
<TextInput autoCorrect={false} style={styles.default} />
</WithLabel>
</View>
);
}
},
{
title: 'Keyboard types',
render: function() {
var keyboardTypes = [
'default',
//'ascii-capable',
//'numbers-and-punctuation',
'url',
'number-pad',
'phone-pad',
//'name-phone-pad',
'email-address',
//'decimal-pad',
//'twitter',
'web-search',
'numeric',
];
var examples = keyboardTypes.map((type) => {
return (
<WithLabel key={type} label={type}>
<TextInput
keyboardType={type}
style={styles.default}
/>
</WithLabel>
);
});
return <View>{examples}</View>;
}
},
{
title: 'Keyboard appearance',
render: function() {
var keyboardAppearance = [
'default',
'light',
'dark',
];
var examples = keyboardAppearance.map((type) => {
return (
<WithLabel key={type} label={type}>
<TextInput
keyboardAppearance={type}
style={styles.default}
/>
</WithLabel>
);
});
return <View>{examples}</View>;
}
},
{
title: 'Return key types',
render: function() {
var returnKeyTypes = [
'default',
'go',
'google',
'join',
'next',
'route',
'search',
'send',
'yahoo',
'done',
'emergency-call',
];
var examples = returnKeyTypes.map((type) => {
return (
<WithLabel key={type} label={type}>
<TextInput
returnKeyType={type}
style={styles.default}
/>
</WithLabel>
);
});
return <View>{examples}</View>;
}
},
{
title: 'Enable return key automatically',
render: function() {
return (
<View>
<WithLabel label="true">
<TextInput enablesReturnKeyAutomatically={true} style={styles.default} />
</WithLabel>
</View>
);
}
},
{
title: 'Secure text entry',
render: function() {
return (
<View>
<WithLabel label="true">
<TextInput secureTextEntry={true} style={styles.default} defaultValue="abc" />
</WithLabel>
</View>
);
}
},
{
title: 'Event handling',
render: function(): React.Element<any> { return <TextEventsExample />; },
},
{
title: 'Colored input text',
render: function() {
return (
<View>
<TextInput
style={[styles.default, {color: 'blue'}]}
defaultValue="Blue"
/>
<TextInput
style={[styles.default, {color: 'green'}]}
defaultValue="Green"
/>
</View>
);
}
},
{
title: 'Colored highlight/cursor for text input',
render: function() {
return (
<View>
<TextInput
style={styles.default}
selectionColor={"green"}
defaultValue="Highlight me"
/>
<TextInput
style={styles.default}
selectionColor={"rgba(86, 76, 205, 1)"}
defaultValue="Highlight me"
/>
</View>
);
}
},
{
title: 'Clear button mode',
render: function () {
return (
<View>
<WithLabel label="never">
<TextInput
style={styles.default}
clearButtonMode="never"
/>
</WithLabel>
<WithLabel label="while editing">
<TextInput
style={styles.default}
clearButtonMode="while-editing"
/>
</WithLabel>
<WithLabel label="unless editing">
<TextInput
style={styles.default}
clearButtonMode="unless-editing"
/>
</WithLabel>
<WithLabel label="always">
<TextInput
style={styles.default}
clearButtonMode="always"
/>
</WithLabel>
</View>
);
}
},
{
title: 'Clear and select',
render: function() {
return (
<View>
<WithLabel label="clearTextOnFocus">
<TextInput
placeholder="text is cleared on focus"
defaultValue="text is cleared on focus"
style={styles.default}
clearTextOnFocus={true}
/>
</WithLabel>
<WithLabel label="selectTextOnFocus">
<TextInput
placeholder="text is selected on focus"
defaultValue="text is selected on focus"
style={styles.default}
selectTextOnFocus={true}
/>
</WithLabel>
</View>
);
}
},
{
title: 'Blur on submit',
render: function(): React.Element<any> { return <BlurOnSubmitExample />; },
},
{
title: 'Multiline blur on submit',
render: function() {
return (
<View>
<TextInput
style={styles.multiline}
placeholder="blurOnSubmit = true"
returnKeyType="next"
blurOnSubmit={true}
multiline={true}
onSubmitEditing={event => alert(event.nativeEvent.text)}
/>
</View>
);
}
},
{
title: 'Multiline',
render: function() {
return (
<View>
<TextInput
placeholder="multiline text input"
multiline={true}
style={styles.multiline}
/>
<TextInput
placeholder="multiline text input with font styles and placeholder"
multiline={true}
clearTextOnFocus={true}
autoCorrect={true}
autoCapitalize="words"
placeholderTextColor="red"
keyboardType="url"
style={[styles.multiline, styles.multilineWithFontStyles]}
/>
<TextInput
placeholder="multiline text input with max length"
maxLength={5}
multiline={true}
style={styles.multiline}
/>
<TextInput
placeholder="uneditable multiline text input"
editable={false}
multiline={true}
style={styles.multiline}
/>
<TextInput
defaultValue="uneditable multiline text input with phone number detection: 88888888."
editable={false}
multiline={true}
style={styles.multiline}
dataDetectorTypes="phoneNumber"
/>
</View>
);
}
},
{
title: 'Number of lines',
render: function() {
return (
<View>
<TextInput
multiline={true}
numberOfLines={4}
style={[ styles.multiline, { height: 'auto' } ]}
/>
</View>
);
}
},
{
title: 'Auto-expanding',
render: function() {
return (
<View>
<AutoExpandingTextInput
placeholder="height increases with content"
enablesReturnKeyAutomatically={true}
returnKeyType="default"
/>
</View>
);
}
},
{
title: 'Attributed text',
render: function() {
return <TokenizedTextExample />;
}
},
{
title: 'Text selection & cursor placement',
render: function() {
return (
<View>
<SelectionExample
style={styles.default}
value="text selection can be changed"
/>
<SelectionExample
multiline
style={styles.multiline}
value={"multiline text selection\ncan also be changed"}
/>
</View>
);
}
},
{
title: 'TextInput maxLength',
render: function() {
return (
<View>
<WithLabel label="maxLength: 5">
<TextInput
maxLength={5}
style={styles.default}
/>
</WithLabel>
<WithLabel label="maxLength: 5 with placeholder">
<TextInput
maxLength={5}
placeholder="ZIP code entry"
style={styles.default}
/>
</WithLabel>
<WithLabel label="maxLength: 5 with default value already set">
<TextInput
maxLength={5}
defaultValue="94025"
style={styles.default}
/>
</WithLabel>
<WithLabel label="maxLength: 5 with very long default value already set">
<TextInput
maxLength={5}
defaultValue="9402512345"
style={styles.default}
/>
</WithLabel>
</View>
);
}
}
];
examples.forEach((example) => {
storiesOf('component: TextInput', module)
.add(example.title, () => example.render())
});

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

@@ -23,7 +23,7 @@ var {
AppRegistry,
StyleSheet,
Text,
TouchableBounce,
TouchableOpacity,
View,
} = ReactNative;
@@ -139,9 +139,9 @@ class GameEndOverlay extends React.Component {
return (
<View style={styles.overlay}>
<Text style={styles.overlayMessage}>{message}</Text>
<TouchableBounce onPress={this.props.onRestart} style={styles.tryAgain}>
<TouchableOpacity onPress={this.props.onRestart} style={styles.tryAgain}>
<Text style={styles.tryAgainText}>Try Again?</Text>
</TouchableBounce>
</TouchableOpacity>
</View>
);
}

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,62 +0,0 @@
const webpack = require('webpack')
const testEntry = 'src/tests.webpack.js'
module.exports = function (config) {
config.set({
browsers: process.env.TRAVIS ? [ 'Firefox' ] : [ 'Chrome' ],
browserNoActivityTimeout: 60000,
client: {
captureConsole: true,
mocha: { ui: 'tdd' },
useIframe: true
},
files: [
testEntry
],
frameworks: [ 'mocha' ],
plugins: [
'karma-chrome-launcher',
'karma-firefox-launcher',
'karma-mocha',
'karma-mocha-reporter',
'karma-sourcemap-loader',
'karma-webpack'
],
preprocessors: {
[testEntry]: [ 'webpack', 'sourcemap' ]
},
reporters: process.env.TRAVIS ? [ 'dots' ] : [ 'mocha' ],
singleRun: true,
webpack: {
devtool: 'inline-source-map',
// required by 'enzyme'
externals: {
'cheerio': 'window',
'react/addons': true,
'react/lib/ExecutionEnvironment': true,
'react/lib/ReactContext': true
},
module: {
loaders: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel',
query: { cacheDirectory: true }
}
]
},
plugins: [
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify('test')
}
})
]
},
webpackServer: {
noInfo: true
}
})
}

View File

@@ -1,65 +1,63 @@
{
"name": "react-native-web",
"version": "0.0.43",
"version": "0.0.58",
"description": "React Native for Web",
"main": "dist/index.js",
"files": [
"dist"
"dist",
"src",
"!**/__tests__"
],
"scripts": {
"build": "del ./dist && mkdir dist && babel src -d dist --ignore **/__tests__",
"build:examples": "build-storybook -o dist-examples -c ./examples/.storybook",
"build:umd": "webpack --config webpack.config.js --sort-assets-by --progress",
"deploy:examples": "git checkout gh-pages && rm -rf ./storybook && mv dist-examples storybook && git add -A && git commit -m \"Storybook deploy\" && git push origin gh-pages && git checkout -",
"examples": "start-storybook -p 9001 -c ./examples/.storybook",
"examples": "start-storybook -p 9001 -c ./examples/.storybook --dont-track",
"lint": "eslint src",
"prepublish": "npm run build && npm run build:umd",
"test": "karma start karma.config.js",
"test:watch": "npm run test -- --no-single-run"
"test": "npm run lint && npm run test:jest",
"test:jest": "jest",
"test:watch": "npm run test:jest -- --watch"
},
"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",
"array-find-index": "^1.0.2",
"babel-runtime": "^6.11.6",
"debounce": "^1.0.0",
"deep-assign": "^2.0.0",
"fbjs": "^0.8.4",
"inline-style-prefixer": "^2.0.1",
"react-dom": "~15.3.2",
"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-plugin-transform-react-remove-prop-types": "^0.2.11",
"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-browserstack-launcher": "^1.0.1",
"karma-chrome-launcher": "^1.0.1",
"karma-firefox-launcher": "^1.0.0",
"karma-mocha": "^1.1.1",
"karma-mocha-reporter": "^2.0.4",
"karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "^1.7.0",
"mocha": "^2.5.3",
"jest": "^16.0.2",
"node-libs-browser": "^0.5.3",
"react": "^15.2.0",
"react-addons-test-utils": "^15.2.0",
"react": "~15.3.2",
"react-addons-test-utils": "~15.3.2",
"react-test-renderer": "~15.3.2",
"url-loader": "^0.5.7",
"webpack": "^1.13.1"
"webpack": "^1.13.2",
"webpack-bundle-analyzer": "^1.5.3"
},
"peerDependencies": {
"react": "^15.1.0"
"react": "~15.3.2"
},
"author": "Nicolas Gallagher",
"license": "BSD-3-Clause",
@@ -75,5 +73,9 @@
"react-component",
"react-native",
"web"
]
],
"jest": {
"testEnvironment": "jsdom",
"timers": "fake"
}
}

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,15 @@
/* eslint-env mocha */
/* eslint-env jasmine, jest */
import assert from 'assert'
import { prerenderApplication } from '../renderApplication'
import React from 'react'
import { getApplication } from '../renderApplication';
import React from 'react';
const component = () => <div />
const component = () => <div />;
suite('apis/AppRegistry/renderApplication', () => {
test('prerenderApplication', () => {
const { html, styleElement } = prerenderApplication(component, {})
describe('apis/AppRegistry/renderApplication', () => {
test('getApplication', () => {
const { element, stylesheet } = getApplication(component, {});
assert.ok(html.indexOf('<div ') > -1)
assert.equal(styleElement.type, 'style')
})
})
expect(element).toBeTruthy();
expect(stylesheet.type).toEqual('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 { unmountComponentAtNode } from 'react/lib/ReactMount';
import renderApplication, { getApplication } from './renderApplication';
const runnables = {}
const runnables = {};
type ComponentProvider = () => Component<any, any, any>
@@ -26,67 +26,67 @@ type AppConfig = {
*/
class AppRegistry {
static getAppKeys(): Array<string> {
return Object.keys(runnables)
return Object.keys(runnables);
}
static prerenderApplication(appKey: string, appParameters?: Object): string {
static getApplication(appKey: string, appParameters?: Object): string {
invariant(
runnables[appKey] && runnables[appKey].prerender,
runnables[appKey] && runnables[appKey].getApplication,
`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].getApplication(appParameters);
}
static registerComponent(appKey: string, getComponentFunc: ComponentProvider): string {
runnables[appKey] = {
run: ({ initialProps, rootTag }) => renderApplication(getComponentFunc(), initialProps, rootTag),
prerender: ({ initialProps } = {}) => prerenderApplication(getComponentFunc(), initialProps)
}
return appKey
getApplication: ({ initialProps } = {}) => getApplication(getComponentFunc(), initialProps),
run: ({ initialProps, rootTag }) => renderApplication(getComponentFunc(), initialProps, rootTag)
};
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)
unmountComponentAtNode(rootTag);
}
}
module.exports = AppRegistry
module.exports = AppRegistry;

View File

@@ -6,15 +6,14 @@
* @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 { render } from 'react/lib/ReactMount';
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,18 +21,17 @@ export default function renderApplication(RootComponent: Component, initialProps
rootComponent={RootComponent}
rootTag={rootTag}
/>
)
ReactDOM.render(component, rootTag)
);
render(component, rootTag);
}
export function prerenderApplication(RootComponent: Component, initialProps: Object): string {
const component = (
export function getApplication(RootComponent: Component, initialProps: Object): Object {
const element = (
<ReactNativeApp
initialProps={initialProps}
rootComponent={RootComponent}
/>
)
const html = ReactDOMServer.renderToString(component)
const styleElement = StyleSheet.render()
return { html, styleElement }
);
const stylesheet = StyleSheet.render();
return { element, stylesheet };
}

View File

@@ -1,31 +1,30 @@
/* eslint-env mocha */
/* eslint-env jasmine, jest */
import AppState from '..'
import assert from 'assert'
import AppState from '..';
suite('apis/AppState', () => {
const handler = () => {}
describe('apis/AppState', () => {
const handler = () => {};
teardown(() => {
try { AppState.removeEventListener('change', handler) } catch (e) {}
})
afterEach(() => {
try { AppState.removeEventListener('change', handler); } catch (e) {}
});
suite('addEventListener', () => {
describe('addEventListener', () => {
test('throws if the provided "eventType" is not supported', () => {
assert.throws(() => AppState.addEventListener('foo', handler))
assert.doesNotThrow(() => AppState.addEventListener('change', handler))
})
})
expect(() => AppState.addEventListener('foo', handler)).toThrow();
expect(() => AppState.addEventListener('change', handler)).not.toThrow();
});
});
suite('removeEventListener', () => {
describe('removeEventListener', () => {
test('throws if the handler is not registered', () => {
assert.throws(() => AppState.removeEventListener('change', handler))
})
expect(() => AppState.removeEventListener('change', handler)).toThrow();
});
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);
expect(() => AppState.removeEventListener('foo', handler)).toThrow();
expect(() => AppState.removeEventListener('change', handler)).not.toThrow();
});
});
});

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 'array-find-index';
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

@@ -0,0 +1,11 @@
exports[`apis/AsyncStorage mergeLocalStorageItem should have same behavior as react-native 1`] = `
Object {
"age": 31,
"name": "Chris",
"traits": Object {
"eyes": "blue",
"hair": "brown",
"shoe_size": 10,
},
}
`;

View File

@@ -1,5 +1,74 @@
/* eslint-env mocha */
/* eslint-env jasmine, jest */
import AsyncStorage from '..';
suite('apis/AsyncStorage', () => {
test.skip('NO TEST COVERAGE', () => {})
})
const waterfall = (fns, cb) => {
const _waterfall = (...args) => {
const fn = (fns || []).shift();
if (typeof fn === 'function') {
fn(...args, (err, ...nextArgs) => {
if (err) {
return cb(err);
} else {
return _waterfall(...nextArgs);
}
});
} else {
cb(null, ...args);
}
};
_waterfall();
};
const obj = {};
const mockLocalStorage = {
getItem(key) {
return obj[key];
},
setItem(key, value) {
obj[key] = value;
}
};
const originalLocalStorage = window.localStorage;
describe('apis/AsyncStorage', () => {
describe('mergeLocalStorageItem', () => {
test('should have same behavior as react-native', (done) => {
window.localStorage = mockLocalStorage;
// https://facebook.github.io/react-native/docs/asyncstorage.html
const UID123_object = {
name: 'Chris',
age: 30,
traits: { hair: 'brown', eyes: 'brown' }
};
const UID123_delta = {
age: 31,
traits: { eyes: 'blue', shoe_size: 10 }
};
waterfall([
(cb) => {
AsyncStorage.setItem('UID123', JSON.stringify(UID123_object))
.then(() => cb(null))
.catch(cb);
},
(cb) => {
AsyncStorage.mergeItem('UID123', JSON.stringify(UID123_delta))
.then(() => cb(null))
.catch(cb);
},
(cb) => {
AsyncStorage.getItem('UID123')
.then((result) => {
cb(null, JSON.parse(result));
})
.catch(cb);
}
], (err, result) => {
expect(err).toEqual(null);
expect(result).toMatchSnapshot();
window.localStorage = originalLocalStorage;
done();
});
});
});
});

View File

@@ -3,14 +3,15 @@
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*/
import merge from 'deep-assign';
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(merge({}, oldObject, newObject));
window.localStorage.setItem(key, nextValue);
};
class AsyncStorage {
/**
@@ -19,12 +20,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 +34,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 +53,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 +67,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 +82,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 +97,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 +122,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 +136,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 +150,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 */
/* eslint-env jasmine, jest */
suite('apis/Dimensions', () => {
test.skip('NO TEST COVERAGE', () => {})
})
describe('apis/Dimensions', () => {
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 'debounce';
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
import invariant from 'fbjs/lib/invariant';
const win = ExecutionEnvironment.canUseDOM ? window : { screen: {} }
const win = ExecutionEnvironment.canUseDOM ? window : { screen: {} };
const dimensions = {}
const dimensions = {};
class Dimensions {
static get(dimension: string): Object {
invariant(dimensions[dimension], 'No dimension set for key ' + dimension)
return dimensions[dimension]
invariant(dimensions[dimension], `No dimension set for key ${dimension}`);
return dimensions[dimension];
}
static set(): void {
@@ -26,18 +26,18 @@ class Dimensions {
height: win.innerHeight,
scale: win.devicePixelRatio || 1,
width: win.innerWidth
}
};
dimensions.screen = {
fontScale: 1,
height: win.screen.height,
scale: win.devicePixelRatio || 1,
width: win.screen.width
}
};
}
}
Dimensions.set()
ExecutionEnvironment.canUseDOM && window.addEventListener('resize', debounce(Dimensions.set, 50))
Dimensions.set();
ExecutionEnvironment.canUseDOM && window.addEventListener('resize', debounce(Dimensions.set, 50));
module.exports = Dimensions
module.exports = Dimensions;

View File

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

View File

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

View File

@@ -5,14 +5,13 @@
* @flow
*/
import keyMirror from 'fbjs/lib/keyMirror'
import invariant from 'fbjs/lib/invariant'
import invariant from 'fbjs/lib/invariant';
const InteractionManager = {
Events: keyMirror({
interactionStart: true,
interactionComplete: true
}),
Events: {
interactionStart: 'interactionStart',
interactionComplete: 'interactionComplete'
},
/**
* Schedule a function to run after all interactions have completed.
@@ -21,15 +20,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 +38,10 @@ const InteractionManager = {
invariant(
!!handle,
'Must provide a handle to clear.'
)
);
},
addListener: () => {}
}
};
module.exports = InteractionManager
module.exports = InteractionManager;

36
src/apis/Linking/index.js Normal file
View File

@@ -0,0 +1,36 @@
const Linking = {
addEventListener() {},
removeEventListener() {},
canOpenUrl() { return true; },
getInitialUrl() { return ''; },
openURL(url) {
iframeOpen(url);
}
};
/**
* Tabs opened using JavaScript may redirect the parent tab using
* `window.opener.location`, ignoring cross-origin restrictions and enabling
* phishing attacks.
*
* Safari requires that we open the url by injecting a hidden iframe that calls
* window.open(), then removes the iframe from the DOM.
*
* https://mathiasbynens.github.io/rel-noopener/
*/
const iframeOpen = (url) => {
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
document.body.appendChild(iframe);
const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
const script = iframeDoc.createElement('script');
script.text = `
window.parent = null; window.top = null; window.frameElement = null;
var child = window.open("${url}"); child.opener = null;
`;
iframeDoc.body.appendChild(script);
document.body.removeChild(iframe);
};
module.exports = Linking;

View File

@@ -1,5 +1,32 @@
/* eslint-env mocha */
/* eslint-env jasmine, jest */
suite('apis/NetInfo', () => {
test.skip('NO TEST COVERAGE', () => {})
})
import NetInfo from '..';
describe('apis/NetInfo', () => {
describe('isConnected', () => {
const handler = () => {};
afterEach(() => {
try { NetInfo.isConnected.removeEventListener('change', handler); } catch (e) {}
});
describe('addEventListener', () => {
test('throws if the provided "eventType" is not supported', () => {
expect(() => NetInfo.isConnected.addEventListener('foo', handler)).toThrow();
expect(() => NetInfo.isConnected.addEventListener('change', handler)).not.toThrow();
});
});
describe('removeEventListener', () => {
test('throws if the handler is not registered', () => {
expect(() => NetInfo.isConnected.removeEventListener('change', handler)).toThrow;
});
test('throws if the provided "eventType" is not supported', () => {
NetInfo.isConnected.addEventListener('change', handler);
expect(() => NetInfo.isConnected.removeEventListener('foo', handler)).toThrow;
expect(() => NetInfo.isConnected.removeEventListener('change', handler)).not.toThrow;
});
});
});
});

View File

@@ -6,16 +6,19 @@
* @flow
*/
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment'
import invariant from 'fbjs/lib/invariant'
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
import findIndex from 'array-find-index';
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' ];
const connectionListeners = [];
/**
* Navigator online: https://developer.mozilla.org/en-US/docs/Web/API/NavigatorOnLine/onLine
@@ -23,63 +26,74 @@ 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);
const onlineCallback = () => handler(true);
const offlineCallback = () => handler(false);
connectionListeners.push([ handler, onlineCallback, offlineCallback ]);
window.addEventListener('online', onlineCallback, false);
window.addEventListener('offline', offlineCallback, 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);
const listenerIndex = findIndex(connectionListeners, (pair) => pair[0] === handler);
invariant(listenerIndex !== -1, 'Trying to remove NetInfo connection listener for unregistered handler');
const [ , onlineCallback, offlineCallback ] = connectionListeners[listenerIndex];
window.removeEventListener('online', onlineCallback, false);
window.removeEventListener('offline', offlineCallback, false);
connectionListeners.splice(listenerIndex, 1);
},
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 */
/* eslint-env jasmine, jest */
suite('apis/PixelRatio', () => {
test.skip('NO TEST COVERAGE', () => {})
})
describe('apis/PixelRatio', () => {
test.skip('NO TEST COVERAGE', () => {});
});

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,18 @@
exports[`apis/StyleSheet/expandStyle shortform -> longform 1`] = `
Object {
"borderBottomColor": "white",
"borderBottomStyle": "solid",
"borderBottomWidth": "1px",
"borderLeftStyle": "solid",
"borderLeftWidth": "0px",
"borderRightStyle": "solid",
"borderRightWidth": "0px",
"borderTopStyle": "solid",
"borderTopWidth": "0px",
"boxSizing": "border-box",
"marginBottom": "25px",
"marginLeft": "10px",
"marginRight": "10px",
"marginTop": "50px",
}
`;

View File

@@ -0,0 +1,107 @@
exports[`apis/StyleSheet/i18nStyle LTR mode does not auto-flip 1`] = `
Object {
"borderBottomLeftRadius": 20,
"borderBottomRightRadius": "2rem",
"borderLeftColor": "red",
"borderLeftStyle": "solid",
"borderLeftWidth": 5,
"borderRightColor": "blue",
"borderRightStyle": "dotted",
"borderRightWidth": 6,
"borderTopLeftRadius": 10,
"borderTopRightRadius": "1rem",
"left": 1,
"marginLeft": 7,
"marginRight": 8,
"paddingLeft": 9,
"paddingRight": 10,
"right": 2,
"textAlign": "left",
"textShadowOffset": Object {
"height": 10,
"width": "1rem",
},
"writingDirection": "ltr",
}
`;
exports[`apis/StyleSheet/i18nStyle LTR mode normalizes properties 1`] = `
Object {
"borderBottomLeftRadius": 20,
"borderBottomRightRadius": "2rem",
"borderLeftColor": "red",
"borderLeftStyle": "solid",
"borderLeftWidth": 5,
"borderRightColor": "blue",
"borderRightStyle": "dotted",
"borderRightWidth": 6,
"borderTopLeftRadius": 10,
"borderTopRightRadius": "1rem",
"left": 1,
"marginLeft": 7,
"marginRight": 8,
"paddingLeft": 9,
"paddingRight": 10,
"right": 2,
"textAlign": "left",
"textShadowOffset": Object {
"height": 10,
"width": "1rem",
},
"writingDirection": "ltr",
}
`;
exports[`apis/StyleSheet/i18nStyle RTL mode does auto-flip 1`] = `
Object {
"borderBottomLeftRadius": "2rem",
"borderBottomRightRadius": 20,
"borderLeftColor": "blue",
"borderLeftStyle": "dotted",
"borderLeftWidth": 6,
"borderRightColor": "red",
"borderRightStyle": "solid",
"borderRightWidth": 5,
"borderTopLeftRadius": "1rem",
"borderTopRightRadius": 10,
"left": 2,
"marginLeft": 8,
"marginRight": 7,
"paddingLeft": 10,
"paddingRight": 9,
"right": 1,
"textAlign": "right",
"textShadowOffset": Object {
"height": 10,
"width": "-1rem",
},
"writingDirection": "rtl",
}
`;
exports[`apis/StyleSheet/i18nStyle RTL mode normalizes properties 1`] = `
Object {
"borderBottomLeftRadius": 20,
"borderBottomRightRadius": "2rem",
"borderLeftColor": "red",
"borderLeftStyle": "solid",
"borderLeftWidth": 5,
"borderRightColor": "blue",
"borderRightStyle": "dotted",
"borderRightWidth": 6,
"borderTopLeftRadius": 10,
"borderTopRightRadius": "1rem",
"left": 1,
"marginLeft": 7,
"marginRight": 8,
"paddingLeft": 9,
"paddingRight": 10,
"right": 2,
"textAlign": "left",
"textShadowOffset": Object {
"height": 10,
"width": "-1rem",
},
"writingDirection": "ltr",
}
`;

View File

@@ -0,0 +1,10 @@
exports[`apis/StyleSheet resolve 1`] = `
Object {
"className": "test __style_df __style_pebn",
"style": Object {
"display": null,
"opacity": 1,
"pointerEvents": null,
},
}
`;

View File

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

View File

@@ -1,11 +1,10 @@
/* eslint-env mocha */
/* eslint-env jasmine, jest */
import assert from 'assert'
import expandStyle from '../expandStyle'
import expandStyle from '../expandStyle';
suite('apis/StyleSheet/expandStyle', () => {
describe('apis/StyleSheet/expandStyle', () => {
test('shortform -> longform', () => {
const initial = {
const style = {
borderStyle: 'solid',
boxSizing: 'border-box',
borderBottomColor: 'white',
@@ -14,53 +13,36 @@ suite('apis/StyleSheet/expandStyle', () => {
marginTop: 50,
marginVertical: 25,
margin: 10
}
};
const expected = {
borderBottomStyle: 'solid',
borderLeftStyle: 'solid',
borderRightStyle: 'solid',
boxSizing: 'border-box',
borderBottomColor: 'white',
borderTopStyle: 'solid',
borderTopWidth: '0px',
borderLeftWidth: '0px',
borderRightWidth: '0px',
borderBottomWidth: '1px',
marginTop: '50px',
marginBottom: '25px',
marginLeft: '10px',
marginRight: '10px'
}
assert.deepEqual(expandStyle(initial), expected)
})
expect(expandStyle(style)).toMatchSnapshot();
});
test('textAlignVertical', () => {
const initial = {
textAlignVertical: 'center'
}
};
const expected = {
verticalAlign: 'middle'
}
};
assert.deepEqual(expandStyle(initial), expected)
})
expect(expandStyle(initial)).toEqual(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)
})
})
expect(expandStyle(initial)).toEqual(expected);
});
});

View File

@@ -1,10 +1,9 @@
/* eslint-env mocha */
/* eslint-env jasmine, jest */
import assert from 'assert'
import I18nManager from '../../I18nManager'
import i18nStyle from '../i18nStyle'
import I18nManager from '../../I18nManager';
import i18nStyle from '../i18nStyle';
const initial = {
const style = {
borderLeftColor: 'red',
borderRightColor: 'blue',
borderTopLeftRadius: 10,
@@ -24,68 +23,46 @@ const initial = {
textAlign: 'left',
textShadowOffset: { width: '1rem', height: 10 },
writingDirection: 'ltr'
}
};
const initialNoI18n = Object.keys(initial).reduce((acc, prop) => {
const newProp = `${prop}$noI18n`
acc[newProp] = initial[prop]
return acc
}, {})
const styleNoI18n = Object.keys(style).reduce((acc, prop) => {
const newProp = `${prop}$noI18n`;
acc[newProp] = style[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'
}
describe('apis/StyleSheet/i18nStyle', () => {
describe('LTR mode', () => {
beforeEach(() => {
I18nManager.allowRTL(false);
});
suite('apis/StyleSheet/i18nStyle', () => {
suite('LTR mode', () => {
setup(() => {
I18nManager.allowRTL(false)
})
teardown(() => {
I18nManager.allowRTL(true)
})
afterEach(() => {
I18nManager.allowRTL(true);
});
test('does not auto-flip', () => {
assert.deepEqual(i18nStyle(initial), initial)
})
expect(i18nStyle(style)).toMatchSnapshot();
});
test('normalizes properties', () => {
assert.deepEqual(i18nStyle(initialNoI18n), initial)
})
})
expect(i18nStyle(styleNoI18n)).toMatchSnapshot();
});
});
suite('RTL mode', () => {
setup(() => {
I18nManager.forceRTL(true)
})
describe('RTL mode', () => {
beforeEach(() => {
I18nManager.forceRTL(true);
});
teardown(() => {
I18nManager.forceRTL(false)
})
afterEach(() => {
I18nManager.forceRTL(false);
});
test('does auto-flip', () => {
assert.deepEqual(i18nStyle(initial), expected)
})
expect(i18nStyle(style)).toMatchSnapshot();
});
test('normalizes properties', () => {
assert.deepEqual(i18nStyle(initialNoI18n), initial)
})
})
})
expect(i18nStyle(styleNoI18n)).toMatchSnapshot();
});
});
});

View File

@@ -1,71 +1,64 @@
/* eslint-env mocha */
/* eslint-env jasmine, jest */
import assert from 'assert'
import { getDefaultStyleSheet } from '../css'
import isPlainObject from 'lodash/isPlainObject'
import StyleSheet from '..'
import { getDefaultStyleSheet } from '../css';
import StyleSheet from '..';
suite('apis/StyleSheet', () => {
setup(() => {
StyleSheet._reset()
})
const isPlainObject = (x) => {
const toString = Object.prototype.toString;
let proto;
/* eslint-disable */
return (
toString.call(x) === '[object Object]' &&
(proto = Object.getPrototypeOf(x), proto === null || proto === Object.getPrototypeOf({}))
);
/* eslint-enable */
};
describe('apis/StyleSheet', () => {
beforeEach(() => {
StyleSheet._reset();
});
test('absoluteFill', () => {
assert(Number.isInteger(StyleSheet.absoluteFill) === true)
})
expect(Number.isInteger(StyleSheet.absoluteFill) === true).toBeTruthy();
});
test('absoluteFillObject', () => {
assert.ok(isPlainObject(StyleSheet.absoluteFillObject) === true)
})
expect(isPlainObject(StyleSheet.absoluteFillObject) === true).toBeTruthy();
});
suite('create', () => {
describe('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 } });
expect(Number.isInteger(style.root) === true).toBeTruthy();
});
test('renders a style sheet in the browser', () => {
StyleSheet.create({ root: { color: 'red' } })
assert.equal(
document.getElementById('__react-native-style').textContent,
getDefaultStyleSheet()
)
})
})
StyleSheet.create({ root: { color: 'red' } });
expect(document.getElementById('react-native-style__').textContent).toEqual(getDefaultStyleSheet());
});
});
test('flatten', () => {
assert(typeof StyleSheet.flatten === 'function')
})
expect(typeof StyleSheet.flatten === 'function').toBeTruthy();
});
test('hairlineWidth', () => {
assert(Number.isInteger(StyleSheet.hairlineWidth) === true)
})
expect(Number.isInteger(StyleSheet.hairlineWidth) === true).toBeTruthy();
});
test('render', () => {
assert.equal(
StyleSheet.render().props.dangerouslySetInnerHTML.__html,
getDefaultStyleSheet()
)
})
expect(StyleSheet.render().props.dangerouslySetInnerHTML.__html).toEqual(getDefaultStyleSheet());
});
test('resolve', () => {
assert.deepEqual(
StyleSheet.resolve({
className: 'test',
style: {
display: 'flex',
opacity: 1,
pointerEvents: 'box-none'
}
}),
{
className: 'test __style_df __style_pebn',
style: {
display: null,
opacity: 1,
pointerEvents: null
}
expect(StyleSheet.resolve({
className: 'test',
style: {
display: 'flex',
opacity: 1,
pointerEvents: 'box-none'
}
)
})
})
})).toMatchSnapshot();
});
});

View File

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

View File

@@ -1,24 +1,20 @@
/* eslint-env mocha */
/* eslint-env jasmine, jest */
import assert from 'assert'
import processTextShadow from '../processTextShadow'
import processTextShadow from '../processTextShadow';
suite('apis/StyleSheet/processTextShadow', () => {
describe('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
}
)
})
})
expect(processTextShadow(style)).toEqual({
textShadow: '2px 2px 5px red',
textShadowColor: null,
textShadowOffset: null,
textShadowRadius: null
});
});
});

View File

@@ -1,9 +1,8 @@
/* eslint-env mocha */
/* eslint-env jasmine, jest */
import assert from 'assert'
import processTransform from '../processTransform'
import processTransform from '../processTransform';
suite('apis/StyleSheet/processTransform', () => {
describe('apis/StyleSheet/processTransform', () => {
test('transform', () => {
const style = {
transform: [
@@ -11,25 +10,19 @@ suite('apis/StyleSheet/processTransform', () => {
{ translateX: 20 },
{ rotate: '20deg' }
]
}
};
assert.deepEqual(
processTransform(style),
{ transform: 'scaleX(20) translateX(20px) rotate(20deg)' }
)
})
expect(processTransform(style)).toEqual({ 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)',
transformMatrix: null
}
)
})
})
expect(processTransform(style)).toEqual({
transform: 'matrix3d(1,2,3,4,5,6)',
transformMatrix: null
});
});
});

View File

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

View File

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

View File

@@ -1,23 +1,27 @@
const DISPLAY_FLEX_CLASSNAME = '__style_df'
const POINTER_EVENTS_AUTO_CLASSNAME = '__style_pea'
const POINTER_EVENTS_BOX_NONE_CLASSNAME = '__style_pebn'
const POINTER_EVENTS_BOX_ONLY_CLASSNAME = '__style_pebo'
const POINTER_EVENTS_NONE_CLASSNAME = '__style_pen'
const DISPLAY_FLEX_CLASSNAME = '__style_df';
const POINTER_EVENTS_AUTO_CLASSNAME = '__style_pea';
const POINTER_EVENTS_BOX_NONE_CLASSNAME = '__style_pebn';
const POINTER_EVENTS_BOX_ONLY_CLASSNAME = '__style_pebo';
const POINTER_EVENTS_NONE_CLASSNAME = '__style_pen';
/* eslint-disable max-len */
const CSS_RESET =
// reset unwanted styles
'/* React Native */\n' +
'html {font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}\n' +
'body {margin:0}\n' +
'button::-moz-focus-inner, input::-moz-focus-inner {border:0;padding:0}\n' +
'input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration {display:none}'
'html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}\n' +
'body{margin:0}\n' +
'button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}\n' +
'input::-webkit-inner-spin-button,input::-webkit-outer-spin-button,' +
'input::-webkit-search-cancel-button,input::-webkit-search-decoration,' +
'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}`
`.${POINTER_EVENTS_NONE_CLASSNAME}, .${POINTER_EVENTS_BOX_ONLY_CLASSNAME} *, .${POINTER_EVENTS_NONE_CLASSNAME} {pointer-events:none}`;
/* eslint-enable max-len */
const styleAsClassName = {
display: {
@@ -29,10 +33,10 @@ const styleAsClassName = {
'box-only': POINTER_EVENTS_BOX_ONLY_CLASSNAME,
'none': POINTER_EVENTS_NONE_CLASSNAME
}
}
};
export const getDefaultStyleSheet = () => `${CSS_RESET}\n${CSS_HELPERS}`
export const getDefaultStyleSheet = () => `${CSS_RESET}\n${CSS_HELPERS}`;
export const getStyleAsHelperClassName = (prop, value) => {
return styleAsClassName[prop] && styleAsClassName[prop][value]
}
return styleAsClassName[prop] && styleAsClassName[prop][value];
};

View File

@@ -9,9 +9,9 @@
* longfrom equivalents.
*/
import normalizeValue from './normalizeValue'
import normalizeValue from './normalizeValue';
const emptyObject = {}
const emptyObject = {};
const styleShortFormProperties = {
borderColor: [ 'borderTopColor', 'borderRightColor', 'borderBottomColor', 'borderLeftColor' ],
borderRadius: [ 'borderTopLeftRadius', 'borderTopRightRadius', 'borderBottomRightRadius', 'borderBottomLeftRadius' ],
@@ -26,48 +26,48 @@ const styleShortFormProperties = {
paddingVertical: [ 'paddingTop', 'paddingBottom' ],
textDecorationLine: [ 'textDecoration' ],
writingDirection: [ 'direction' ]
}
};
const alphaSort = (arr) => arr.sort((a, b) => {
if (a < b) { return -1 }
if (a > b) { return 1 }
return 0
})
if (a < b) { return -1; }
if (a > b) { return 1; }
return 0;
});
const createStyleReducer = (originalStyle) => {
const originalStyleProps = Object.keys(originalStyle)
const originalStyleProps = Object.keys(originalStyle);
return (style, prop) => {
const value = normalizeValue(prop, originalStyle[prop])
const longFormProperties = styleShortFormProperties[prop]
const value = normalizeValue(prop, originalStyle[prop]);
const longFormProperties = styleShortFormProperties[prop];
// React Native treats `flex:1` like `flex:1 1 auto`
if (prop === 'flex') {
style.flexGrow = value
style.flexShrink = 1
style.flexBasis = 'auto'
style.flexGrow = value;
style.flexShrink = 1;
style.flexBasis = 'auto';
// React Native accepts 'center' as a value
} else if (prop === 'textAlignVertical') {
style.verticalAlign = (value === 'center' ? 'middle' : value)
style.verticalAlign = (value === 'center' ? 'middle' : value);
} else if (longFormProperties) {
longFormProperties.forEach((longForm, i) => {
// the value of any longform property in the original styles takes
// precedence over the shortform's value
if (originalStyleProps.indexOf(longForm) === -1) {
style[longForm] = value
style[longForm] = value;
}
})
});
} else {
style[prop] = value
style[prop] = value;
}
return style
}
}
return style;
};
};
const expandStyle = (style = emptyObject) => {
const sortedStyleProps = alphaSort(Object.keys(style))
const styleReducer = createStyleReducer(style)
return sortedStyleProps.reduce(styleReducer, {})
}
const sortedStyleProps = alphaSort(Object.keys(style));
const styleReducer = createStyleReducer(style);
return sortedStyleProps.reduce(styleReducer, {});
};
module.exports = expandStyle
module.exports = expandStyle;

View File

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

View File

@@ -1,32 +1,31 @@
import * as css from './css'
import createReactStyleObject from './createReactStyleObject'
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment'
import flattenStyle from '../../modules/flattenStyle'
import React from 'react'
import ReactNativePropRegistry from '../../modules/ReactNativePropRegistry'
import StyleSheetValidation from './StyleSheetValidation'
import * as css from './css';
import createReactStyleObject from './createReactStyleObject';
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
import flattenStyle from '../../modules/flattenStyle';
import React from 'react';
import ReactNativePropRegistry from '../../modules/ReactNativePropRegistry';
let styleElement
let shouldInsertStyleSheet = ExecutionEnvironment.canUseDOM
let styleElement;
let shouldInsertStyleSheet = ExecutionEnvironment.canUseDOM;
const STYLE_SHEET_ID = '__react-native-style'
const STYLE_SHEET_ID = 'react-native-style__';
const absoluteFillObject = { position: 'absolute', left: 0, right: 0, top: 0, bottom: 0 }
const absoluteFillObject = { position: 'absolute', left: 0, right: 0, top: 0, bottom: 0 };
const defaultStyleSheet = css.getDefaultStyleSheet()
const defaultStyleSheet = css.getDefaultStyleSheet();
const insertStyleSheet = () => {
// check if the server rendered the style sheet
styleElement = document.getElementById(STYLE_SHEET_ID)
styleElement = document.getElementById(STYLE_SHEET_ID);
// if not, inject the style sheet
if (!styleElement) {
document.head.insertAdjacentHTML(
'afterbegin',
`<style id="${STYLE_SHEET_ID}">${defaultStyleSheet}</style>`
)
shouldInsertStyleSheet = false
);
shouldInsertStyleSheet = false;
}
}
};
module.exports = {
/**
@@ -35,9 +34,9 @@ module.exports = {
*/
_reset() {
if (styleElement) {
document.head.removeChild(styleElement)
styleElement = null
shouldInsertStyleSheet = true
document.head.removeChild(styleElement);
styleElement = null;
shouldInsertStyleSheet = true;
}
},
@@ -47,15 +46,17 @@ module.exports = {
create(styles) {
if (shouldInsertStyleSheet) {
insertStyleSheet()
insertStyleSheet();
}
const result = {}
const result = {};
for (const key in styles) {
StyleSheetValidation.validateStyle(key, styles)
result[key] = ReactNativePropRegistry.register(styles[key])
if (process.env.NODE_ENV !== 'production') {
require('./StyleSheetValidation').validateStyle(key, styles);
}
result[key] = ReactNativePropRegistry.register(styles[key]);
}
return result
return result;
},
hairlineWidth: 1,
@@ -64,7 +65,7 @@ module.exports = {
/* @platform web */
render() {
return <style dangerouslySetInnerHTML={{ __html: defaultStyleSheet }} id={STYLE_SHEET_ID} />
return <style dangerouslySetInnerHTML={{ __html: defaultStyleSheet }} id={STYLE_SHEET_ID} />;
},
/**
@@ -72,17 +73,17 @@ module.exports = {
* @platform web
*/
resolve(props) {
let className = props.className || ''
let style = createReactStyleObject(props.style)
let className = props.className || '';
const style = createReactStyleObject(props.style);
for (const prop in style) {
const value = style[prop]
const replacementClassName = css.getStyleAsHelperClassName(prop, value)
const value = style[prop];
const replacementClassName = css.getStyleAsHelperClassName(prop, value);
if (replacementClassName) {
className += ` ${replacementClassName}`
style[prop] = null
className += ` ${replacementClassName}`;
style[prop] = null;
}
}
return { className, style }
return { className, style };
}
}
};

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,140 +1,158 @@
/* eslint-env mocha */
/* eslint-env jasmine, jest */
import assert from 'assert'
import UIManager from '..'
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(() => {
describe('apis/UIManager', () => {
beforeEach(() => {
// 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
})
afterEach(() => {
document.body.style.margin = defaultBodyMargin;
});
suite('measure', () => {
describe('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);
node.getBoundingClientRect = jest.fn(() => ({ width: 5000, height: 5000, top: 100, left: 100 }));
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)
})
expect(x).toEqual(100);
expect(y).toEqual(100);
expect(width).toEqual(5000);
expect(height).toEqual(5000);
expect(pageX).toEqual(100);
expect(pageY).toEqual(100);
});
// test values account for scroll position
window.scrollTo(200, 200)
window.scrollTo(200, 200);
node.getBoundingClientRect = jest.fn(() => ({ width: 5000, height: 5000, top: -100, left: -100 }));
node.parentNode.getBoundingClientRect = jest.fn(() => ({ top: -200, left: -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)
})
expect(x).toEqual(100);
expect(y).toEqual(100);
expect(width).toEqual(5000);
expect(height).toEqual(5000);
expect(pageX).toEqual(-100);
expect(pageY).toEqual(-100);
});
document.body.removeChild(node)
})
})
document.body.removeChild(node);
});
});
suite('measureLayout', () => {
describe('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);
node.getBoundingClientRect = jest.fn(() => ({
width: 10,
height: 10,
top: 40,
left: 40
}));
UIManager.measureLayout(node, context, () => {}, (x, y, width, height) => {
assert.equal(x, 40)
assert.equal(y, 40)
assert.equal(width, 10)
assert.equal(height, 10)
})
expect(x).toEqual(40);
expect(y).toEqual(40);
expect(width).toEqual(10);
expect(height).toEqual(10);
});
document.body.removeChild(context)
})
})
document.body.removeChild(context);
});
});
suite('measureInWindow', () => {
describe('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);
node.getBoundingClientRect = jest.fn(() => ({
width: 10,
height: 10,
top: 40,
left: 40
}));
UIManager.measureInWindow(node, (x, y, width, height) => {
assert.equal(x, 40)
assert.equal(y, 40)
assert.equal(width, 10)
assert.equal(height, 10)
})
expect(x).toEqual(40);
expect(y).toEqual(40);
expect(width).toEqual(10);
expect(height).toEqual(10);
});
document.body.removeChild(context)
})
})
document.body.removeChild(context);
});
});
suite('updateView', () => {
describe('updateView', () => {
const componentStub = {
_reactInternalInstance: {
_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);
expect(node.getAttribute('class')).toEqual('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);
expect(node.getAttribute('style')).toEqual('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);
expect(node.value).toEqual('expected-text');
UIManager.updateView(node, valueProp)
assert.equal(node.value, 'expected-value')
})
UIManager.updateView(node, valueProp);
expect(node.value).toEqual('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);
expect(node.getAttribute('aria-level')).toEqual('4');
expect(node.getAttribute('data-of-type')).toEqual('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 */
/* eslint-env jasmine, jest */
suite('components/ActivityIndicator', () => {
test.skip('NO TEST COVERAGE', () => {})
})
describe('components/ActivityIndicator', () => {
test.skip('NO TEST COVERAGE', () => {});
});

View File

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

View File

@@ -0,0 +1,5 @@
/* eslint-env jasmine, jest */
describe('components/Button', () => {
test.skip('NO TEST COVERAGE', () => {});
});

View File

@@ -0,0 +1,66 @@
import ColorPropType from '../../propTypes/ColorPropType';
import React, { Component, PropTypes } from 'react';
import StyleSheet from '../../apis/StyleSheet';
import TouchableOpacity from '../Touchable/TouchableOpacity';
import Text from '../Text';
class Button extends Component {
static propTypes = {
accessibilityLabel: PropTypes.string,
color: ColorPropType,
disabled: PropTypes.bool,
onPress: PropTypes.func.isRequired,
title: PropTypes.string.isRequired
};
render() {
const {
accessibilityLabel,
color,
disabled,
onPress,
title
} = this.props;
return (
<TouchableOpacity
accessibilityLabel={accessibilityLabel}
accessibilityRole={'button'}
disabled={disabled}
onPress={onPress}
style={[
styles.button,
color && { backgroundColor: color },
disabled && styles.buttonDisabled
]}>
<Text style={[
styles.text,
disabled && styles.textDisabled
]}>
{title}
</Text>
</TouchableOpacity>
);
}
}
const styles = StyleSheet.create({
button: {
backgroundColor: '#2196F3',
borderRadius: 2
},
text: {
textAlign: 'center',
color: '#fff',
padding: 8,
fontWeight: '500'
},
buttonDisabled: {
backgroundColor: '#dfdfdf'
},
textDisabled: {
color: '#a1a1a1'
}
});
module.exports = Button;

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,13 +1,13 @@
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 = {
module.exports = process.env.NODE_ENV !== 'production' ? {
...BorderPropTypes,
...LayoutPropTypes,
...TransformPropTypes,
@@ -24,4 +24,4 @@ module.exports = {
* @platform web
*/
visibility: hiddenOrVisible
}
} : {};

View File

@@ -0,0 +1,912 @@
exports[`components/Image passes other props through to underlying View 1`] = `
<div
className=" __style_df"
onResponderGrant={[Function]}
role="img"
style={
Object {
"MozBoxSizing": "border-box",
"WebkitAlignItems": "stretch",
"WebkitBoxAlign": "stretch",
"WebkitBoxDirection": "normal",
"WebkitBoxOrient": "vertical",
"WebkitFlexBasis": "auto",
"WebkitFlexDirection": "column",
"WebkitFlexShrink": 0,
"alignItems": "stretch",
"backgroundColor": "transparent",
"backgroundPosition": "center",
"backgroundRepeat": "no-repeat",
"backgroundSize": "cover",
"borderBottomStyle": "solid",
"borderBottomWidth": "0px",
"borderLeftStyle": "solid",
"borderLeftWidth": "0px",
"borderRightStyle": "solid",
"borderRightWidth": "0px",
"borderTopStyle": "solid",
"borderTopWidth": "0px",
"boxSizing": "border-box",
"color": "inherit",
"display": null,
"flexBasis": "auto",
"flexDirection": "column",
"flexShrink": 0,
"font": "inherit",
"listStyle": "none",
"marginBottom": "0px",
"marginLeft": "0px",
"marginRight": "0px",
"marginTop": "0px",
"minHeight": "0px",
"minWidth": "0px",
"msFlexAlign": "stretch",
"msFlexDirection": "column",
"msFlexNegative": 0,
"msPreferredSize": "auto",
"paddingBottom": "0px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingTop": "0px",
"position": "relative",
"textAlign": "inherit",
"textDecoration": "none",
}
} />
`;
exports[`components/Image prop "accessibilityLabel" 1`] = `
<div
aria-label="accessibilityLabel"
className=" __style_df"
role="img"
style={
Object {
"MozBoxSizing": "border-box",
"WebkitAlignItems": "stretch",
"WebkitBoxAlign": "stretch",
"WebkitBoxDirection": "normal",
"WebkitBoxOrient": "vertical",
"WebkitFlexBasis": "auto",
"WebkitFlexDirection": "column",
"WebkitFlexShrink": 0,
"alignItems": "stretch",
"backgroundColor": "transparent",
"backgroundPosition": "center",
"backgroundRepeat": "no-repeat",
"backgroundSize": "cover",
"borderBottomStyle": "solid",
"borderBottomWidth": "0px",
"borderLeftStyle": "solid",
"borderLeftWidth": "0px",
"borderRightStyle": "solid",
"borderRightWidth": "0px",
"borderTopStyle": "solid",
"borderTopWidth": "0px",
"boxSizing": "border-box",
"color": "inherit",
"display": null,
"flexBasis": "auto",
"flexDirection": "column",
"flexShrink": 0,
"font": "inherit",
"listStyle": "none",
"marginBottom": "0px",
"marginLeft": "0px",
"marginRight": "0px",
"marginTop": "0px",
"minHeight": "0px",
"minWidth": "0px",
"msFlexAlign": "stretch",
"msFlexDirection": "column",
"msFlexNegative": 0,
"msPreferredSize": "auto",
"paddingBottom": "0px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingTop": "0px",
"position": "relative",
"textAlign": "inherit",
"textDecoration": "none",
}
} />
`;
exports[`components/Image prop "accessible" 1`] = `
<div
aria-hidden={true}
className=" __style_df"
role="img"
style={
Object {
"MozBoxSizing": "border-box",
"WebkitAlignItems": "stretch",
"WebkitBoxAlign": "stretch",
"WebkitBoxDirection": "normal",
"WebkitBoxOrient": "vertical",
"WebkitFlexBasis": "auto",
"WebkitFlexDirection": "column",
"WebkitFlexShrink": 0,
"alignItems": "stretch",
"backgroundColor": "transparent",
"backgroundPosition": "center",
"backgroundRepeat": "no-repeat",
"backgroundSize": "cover",
"borderBottomStyle": "solid",
"borderBottomWidth": "0px",
"borderLeftStyle": "solid",
"borderLeftWidth": "0px",
"borderRightStyle": "solid",
"borderRightWidth": "0px",
"borderTopStyle": "solid",
"borderTopWidth": "0px",
"boxSizing": "border-box",
"color": "inherit",
"display": null,
"flexBasis": "auto",
"flexDirection": "column",
"flexShrink": 0,
"font": "inherit",
"listStyle": "none",
"marginBottom": "0px",
"marginLeft": "0px",
"marginRight": "0px",
"marginTop": "0px",
"minHeight": "0px",
"minWidth": "0px",
"msFlexAlign": "stretch",
"msFlexDirection": "column",
"msFlexNegative": 0,
"msPreferredSize": "auto",
"paddingBottom": "0px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingTop": "0px",
"position": "relative",
"textAlign": "inherit",
"textDecoration": "none",
}
} />
`;
exports[`components/Image prop "children" 1`] = `
<div
className=" __style_df"
role="img"
style={
Object {
"MozBoxSizing": "border-box",
"WebkitAlignItems": "stretch",
"WebkitBoxAlign": "stretch",
"WebkitBoxDirection": "normal",
"WebkitBoxOrient": "vertical",
"WebkitFlexBasis": "auto",
"WebkitFlexDirection": "column",
"WebkitFlexShrink": 0,
"alignItems": "stretch",
"backgroundColor": "transparent",
"backgroundPosition": "center",
"backgroundRepeat": "no-repeat",
"backgroundSize": "cover",
"borderBottomStyle": "solid",
"borderBottomWidth": "0px",
"borderLeftStyle": "solid",
"borderLeftWidth": "0px",
"borderRightStyle": "solid",
"borderRightWidth": "0px",
"borderTopStyle": "solid",
"borderTopWidth": "0px",
"boxSizing": "border-box",
"color": "inherit",
"display": null,
"flexBasis": "auto",
"flexDirection": "column",
"flexShrink": 0,
"font": "inherit",
"listStyle": "none",
"marginBottom": "0px",
"marginLeft": "0px",
"marginRight": "0px",
"marginTop": "0px",
"minHeight": "0px",
"minWidth": "0px",
"msFlexAlign": "stretch",
"msFlexDirection": "column",
"msFlexNegative": 0,
"msPreferredSize": "auto",
"paddingBottom": "0px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingTop": "0px",
"position": "relative",
"textAlign": "inherit",
"textDecoration": "none",
}
}>
<div
className="unique" />
</div>
`;
exports[`components/Image prop "defaultSource" does not override "height" and "width" styles 1`] = `
<div
className=" __style_df"
role="img"
style={
Object {
"MozBoxSizing": "border-box",
"WebkitAlignItems": "stretch",
"WebkitBoxAlign": "stretch",
"WebkitBoxDirection": "normal",
"WebkitBoxOrient": "vertical",
"WebkitFlexBasis": "auto",
"WebkitFlexDirection": "column",
"WebkitFlexShrink": 0,
"alignItems": "stretch",
"backgroundColor": "transparent",
"backgroundImage": "url(\"https://google.com/favicon.ico\")",
"backgroundPosition": "center",
"backgroundRepeat": "no-repeat",
"backgroundSize": "cover",
"borderBottomStyle": "solid",
"borderBottomWidth": "0px",
"borderLeftStyle": "solid",
"borderLeftWidth": "0px",
"borderRightStyle": "solid",
"borderRightWidth": "0px",
"borderTopStyle": "solid",
"borderTopWidth": "0px",
"boxSizing": "border-box",
"color": "inherit",
"display": null,
"flexBasis": "auto",
"flexDirection": "column",
"flexShrink": 0,
"font": "inherit",
"height": "20px",
"listStyle": "none",
"marginBottom": "0px",
"marginLeft": "0px",
"marginRight": "0px",
"marginTop": "0px",
"minHeight": "0px",
"minWidth": "0px",
"msFlexAlign": "stretch",
"msFlexDirection": "column",
"msFlexNegative": 0,
"msPreferredSize": "auto",
"paddingBottom": "0px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingTop": "0px",
"position": "relative",
"textAlign": "inherit",
"textDecoration": "none",
"width": "40px",
}
} />
`;
exports[`components/Image prop "defaultSource" sets "height" and "width" styles if missing 1`] = `
<div
className=" __style_df"
role="img"
style={
Object {
"MozBoxSizing": "border-box",
"WebkitAlignItems": "stretch",
"WebkitBoxAlign": "stretch",
"WebkitBoxDirection": "normal",
"WebkitBoxOrient": "vertical",
"WebkitFlexBasis": "auto",
"WebkitFlexDirection": "column",
"WebkitFlexShrink": 0,
"alignItems": "stretch",
"backgroundColor": "transparent",
"backgroundImage": "url(\"https://google.com/favicon.ico\")",
"backgroundPosition": "center",
"backgroundRepeat": "no-repeat",
"backgroundSize": "cover",
"borderBottomStyle": "solid",
"borderBottomWidth": "0px",
"borderLeftStyle": "solid",
"borderLeftWidth": "0px",
"borderRightStyle": "solid",
"borderRightWidth": "0px",
"borderTopStyle": "solid",
"borderTopWidth": "0px",
"boxSizing": "border-box",
"color": "inherit",
"display": null,
"flexBasis": "auto",
"flexDirection": "column",
"flexShrink": 0,
"font": "inherit",
"height": "10px",
"listStyle": "none",
"marginBottom": "0px",
"marginLeft": "0px",
"marginRight": "0px",
"marginTop": "0px",
"minHeight": "0px",
"minWidth": "0px",
"msFlexAlign": "stretch",
"msFlexDirection": "column",
"msFlexNegative": 0,
"msPreferredSize": "auto",
"paddingBottom": "0px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingTop": "0px",
"position": "relative",
"textAlign": "inherit",
"textDecoration": "none",
"width": "20px",
}
} />
`;
exports[`components/Image prop "defaultSource" sets background image when value is a string 1`] = `
<div
className=" __style_df"
role="img"
style={
Object {
"MozBoxSizing": "border-box",
"WebkitAlignItems": "stretch",
"WebkitBoxAlign": "stretch",
"WebkitBoxDirection": "normal",
"WebkitBoxOrient": "vertical",
"WebkitFlexBasis": "auto",
"WebkitFlexDirection": "column",
"WebkitFlexShrink": 0,
"alignItems": "stretch",
"backgroundColor": "transparent",
"backgroundImage": "url(\"https://google.com/favicon.ico\")",
"backgroundPosition": "center",
"backgroundRepeat": "no-repeat",
"backgroundSize": "cover",
"borderBottomStyle": "solid",
"borderBottomWidth": "0px",
"borderLeftStyle": "solid",
"borderLeftWidth": "0px",
"borderRightStyle": "solid",
"borderRightWidth": "0px",
"borderTopStyle": "solid",
"borderTopWidth": "0px",
"boxSizing": "border-box",
"color": "inherit",
"display": null,
"flexBasis": "auto",
"flexDirection": "column",
"flexShrink": 0,
"font": "inherit",
"listStyle": "none",
"marginBottom": "0px",
"marginLeft": "0px",
"marginRight": "0px",
"marginTop": "0px",
"minHeight": "0px",
"minWidth": "0px",
"msFlexAlign": "stretch",
"msFlexDirection": "column",
"msFlexNegative": 0,
"msPreferredSize": "auto",
"paddingBottom": "0px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingTop": "0px",
"position": "relative",
"textAlign": "inherit",
"textDecoration": "none",
}
} />
`;
exports[`components/Image prop "defaultSource" sets background image when value is an object 1`] = `
<div
className=" __style_df"
role="img"
style={
Object {
"MozBoxSizing": "border-box",
"WebkitAlignItems": "stretch",
"WebkitBoxAlign": "stretch",
"WebkitBoxDirection": "normal",
"WebkitBoxOrient": "vertical",
"WebkitFlexBasis": "auto",
"WebkitFlexDirection": "column",
"WebkitFlexShrink": 0,
"alignItems": "stretch",
"backgroundColor": "transparent",
"backgroundImage": "url(\"https://google.com/favicon.ico\")",
"backgroundPosition": "center",
"backgroundRepeat": "no-repeat",
"backgroundSize": "cover",
"borderBottomStyle": "solid",
"borderBottomWidth": "0px",
"borderLeftStyle": "solid",
"borderLeftWidth": "0px",
"borderRightStyle": "solid",
"borderRightWidth": "0px",
"borderTopStyle": "solid",
"borderTopWidth": "0px",
"boxSizing": "border-box",
"color": "inherit",
"display": null,
"flexBasis": "auto",
"flexDirection": "column",
"flexShrink": 0,
"font": "inherit",
"height": undefined,
"listStyle": "none",
"marginBottom": "0px",
"marginLeft": "0px",
"marginRight": "0px",
"marginTop": "0px",
"minHeight": "0px",
"minWidth": "0px",
"msFlexAlign": "stretch",
"msFlexDirection": "column",
"msFlexNegative": 0,
"msPreferredSize": "auto",
"paddingBottom": "0px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingTop": "0px",
"position": "relative",
"textAlign": "inherit",
"textDecoration": "none",
"width": undefined,
}
} />
`;
exports[`components/Image prop "resizeMode" value "contain" 1`] = `
<div
className=" __style_df"
role="img"
style={
Object {
"MozBoxSizing": "border-box",
"WebkitAlignItems": "stretch",
"WebkitBoxAlign": "stretch",
"WebkitBoxDirection": "normal",
"WebkitBoxOrient": "vertical",
"WebkitFlexBasis": "auto",
"WebkitFlexDirection": "column",
"WebkitFlexShrink": 0,
"alignItems": "stretch",
"backgroundColor": "transparent",
"backgroundPosition": "center",
"backgroundRepeat": "no-repeat",
"backgroundSize": "contain",
"borderBottomStyle": "solid",
"borderBottomWidth": "0px",
"borderLeftStyle": "solid",
"borderLeftWidth": "0px",
"borderRightStyle": "solid",
"borderRightWidth": "0px",
"borderTopStyle": "solid",
"borderTopWidth": "0px",
"boxSizing": "border-box",
"color": "inherit",
"display": null,
"flexBasis": "auto",
"flexDirection": "column",
"flexShrink": 0,
"font": "inherit",
"listStyle": "none",
"marginBottom": "0px",
"marginLeft": "0px",
"marginRight": "0px",
"marginTop": "0px",
"minHeight": "0px",
"minWidth": "0px",
"msFlexAlign": "stretch",
"msFlexDirection": "column",
"msFlexNegative": 0,
"msPreferredSize": "auto",
"paddingBottom": "0px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingTop": "0px",
"position": "relative",
"textAlign": "inherit",
"textDecoration": "none",
}
} />
`;
exports[`components/Image prop "resizeMode" value "cover" 1`] = `
<div
className=" __style_df"
role="img"
style={
Object {
"MozBoxSizing": "border-box",
"WebkitAlignItems": "stretch",
"WebkitBoxAlign": "stretch",
"WebkitBoxDirection": "normal",
"WebkitBoxOrient": "vertical",
"WebkitFlexBasis": "auto",
"WebkitFlexDirection": "column",
"WebkitFlexShrink": 0,
"alignItems": "stretch",
"backgroundColor": "transparent",
"backgroundPosition": "center",
"backgroundRepeat": "no-repeat",
"backgroundSize": "cover",
"borderBottomStyle": "solid",
"borderBottomWidth": "0px",
"borderLeftStyle": "solid",
"borderLeftWidth": "0px",
"borderRightStyle": "solid",
"borderRightWidth": "0px",
"borderTopStyle": "solid",
"borderTopWidth": "0px",
"boxSizing": "border-box",
"color": "inherit",
"display": null,
"flexBasis": "auto",
"flexDirection": "column",
"flexShrink": 0,
"font": "inherit",
"listStyle": "none",
"marginBottom": "0px",
"marginLeft": "0px",
"marginRight": "0px",
"marginTop": "0px",
"minHeight": "0px",
"minWidth": "0px",
"msFlexAlign": "stretch",
"msFlexDirection": "column",
"msFlexNegative": 0,
"msPreferredSize": "auto",
"paddingBottom": "0px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingTop": "0px",
"position": "relative",
"textAlign": "inherit",
"textDecoration": "none",
}
} />
`;
exports[`components/Image prop "resizeMode" value "none" 1`] = `
<div
className=" __style_df"
role="img"
style={
Object {
"MozBoxSizing": "border-box",
"WebkitAlignItems": "stretch",
"WebkitBoxAlign": "stretch",
"WebkitBoxDirection": "normal",
"WebkitBoxOrient": "vertical",
"WebkitFlexBasis": "auto",
"WebkitFlexDirection": "column",
"WebkitFlexShrink": 0,
"alignItems": "stretch",
"backgroundColor": "transparent",
"backgroundPosition": "center",
"backgroundRepeat": "no-repeat",
"backgroundSize": "auto",
"borderBottomStyle": "solid",
"borderBottomWidth": "0px",
"borderLeftStyle": "solid",
"borderLeftWidth": "0px",
"borderRightStyle": "solid",
"borderRightWidth": "0px",
"borderTopStyle": "solid",
"borderTopWidth": "0px",
"boxSizing": "border-box",
"color": "inherit",
"display": null,
"flexBasis": "auto",
"flexDirection": "column",
"flexShrink": 0,
"font": "inherit",
"listStyle": "none",
"marginBottom": "0px",
"marginLeft": "0px",
"marginRight": "0px",
"marginTop": "0px",
"minHeight": "0px",
"minWidth": "0px",
"msFlexAlign": "stretch",
"msFlexDirection": "column",
"msFlexNegative": 0,
"msPreferredSize": "auto",
"paddingBottom": "0px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingTop": "0px",
"position": "relative",
"textAlign": "inherit",
"textDecoration": "none",
}
} />
`;
exports[`components/Image prop "resizeMode" value "stretch" 1`] = `
<div
className=" __style_df"
role="img"
style={
Object {
"MozBoxSizing": "border-box",
"WebkitAlignItems": "stretch",
"WebkitBoxAlign": "stretch",
"WebkitBoxDirection": "normal",
"WebkitBoxOrient": "vertical",
"WebkitFlexBasis": "auto",
"WebkitFlexDirection": "column",
"WebkitFlexShrink": 0,
"alignItems": "stretch",
"backgroundColor": "transparent",
"backgroundPosition": "center",
"backgroundRepeat": "no-repeat",
"backgroundSize": "100% 100%",
"borderBottomStyle": "solid",
"borderBottomWidth": "0px",
"borderLeftStyle": "solid",
"borderLeftWidth": "0px",
"borderRightStyle": "solid",
"borderRightWidth": "0px",
"borderTopStyle": "solid",
"borderTopWidth": "0px",
"boxSizing": "border-box",
"color": "inherit",
"display": null,
"flexBasis": "auto",
"flexDirection": "column",
"flexShrink": 0,
"font": "inherit",
"listStyle": "none",
"marginBottom": "0px",
"marginLeft": "0px",
"marginRight": "0px",
"marginTop": "0px",
"minHeight": "0px",
"minWidth": "0px",
"msFlexAlign": "stretch",
"msFlexDirection": "column",
"msFlexNegative": 0,
"msPreferredSize": "auto",
"paddingBottom": "0px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingTop": "0px",
"position": "relative",
"textAlign": "inherit",
"textDecoration": "none",
}
} />
`;
exports[`components/Image prop "resizeMode" value "undefined" 1`] = `
<div
className=" __style_df"
role="img"
style={
Object {
"MozBoxSizing": "border-box",
"WebkitAlignItems": "stretch",
"WebkitBoxAlign": "stretch",
"WebkitBoxDirection": "normal",
"WebkitBoxOrient": "vertical",
"WebkitFlexBasis": "auto",
"WebkitFlexDirection": "column",
"WebkitFlexShrink": 0,
"alignItems": "stretch",
"backgroundColor": "transparent",
"backgroundPosition": "center",
"backgroundRepeat": "no-repeat",
"backgroundSize": "cover",
"borderBottomStyle": "solid",
"borderBottomWidth": "0px",
"borderLeftStyle": "solid",
"borderLeftWidth": "0px",
"borderRightStyle": "solid",
"borderRightWidth": "0px",
"borderTopStyle": "solid",
"borderTopWidth": "0px",
"boxSizing": "border-box",
"color": "inherit",
"display": null,
"flexBasis": "auto",
"flexDirection": "column",
"flexShrink": 0,
"font": "inherit",
"listStyle": "none",
"marginBottom": "0px",
"marginLeft": "0px",
"marginRight": "0px",
"marginTop": "0px",
"minHeight": "0px",
"minWidth": "0px",
"msFlexAlign": "stretch",
"msFlexDirection": "column",
"msFlexNegative": 0,
"msPreferredSize": "auto",
"paddingBottom": "0px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingTop": "0px",
"position": "relative",
"textAlign": "inherit",
"textDecoration": "none",
}
} />
`;
exports[`components/Image prop "style" correctly supports "resizeMode" property 1`] = `
<div
className=" __style_df"
role="img"
style={
Object {
"MozBoxSizing": "border-box",
"WebkitAlignItems": "stretch",
"WebkitBoxAlign": "stretch",
"WebkitBoxDirection": "normal",
"WebkitBoxOrient": "vertical",
"WebkitFlexBasis": "auto",
"WebkitFlexDirection": "column",
"WebkitFlexShrink": 0,
"alignItems": "stretch",
"backgroundColor": "transparent",
"backgroundPosition": "center",
"backgroundRepeat": "no-repeat",
"backgroundSize": "contain",
"borderBottomStyle": "solid",
"borderBottomWidth": "0px",
"borderLeftStyle": "solid",
"borderLeftWidth": "0px",
"borderRightStyle": "solid",
"borderRightWidth": "0px",
"borderTopStyle": "solid",
"borderTopWidth": "0px",
"boxSizing": "border-box",
"color": "inherit",
"display": null,
"flexBasis": "auto",
"flexDirection": "column",
"flexShrink": 0,
"font": "inherit",
"listStyle": "none",
"marginBottom": "0px",
"marginLeft": "0px",
"marginRight": "0px",
"marginTop": "0px",
"minHeight": "0px",
"minWidth": "0px",
"msFlexAlign": "stretch",
"msFlexDirection": "column",
"msFlexNegative": 0,
"msPreferredSize": "auto",
"paddingBottom": "0px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingTop": "0px",
"position": "relative",
"textAlign": "inherit",
"textDecoration": "none",
}
} />
`;
exports[`components/Image prop "testID" 1`] = `
<div
className=" __style_df"
data-testid="testID"
role="img"
style={
Object {
"MozBoxSizing": "border-box",
"WebkitAlignItems": "stretch",
"WebkitBoxAlign": "stretch",
"WebkitBoxDirection": "normal",
"WebkitBoxOrient": "vertical",
"WebkitFlexBasis": "auto",
"WebkitFlexDirection": "column",
"WebkitFlexShrink": 0,
"alignItems": "stretch",
"backgroundColor": "transparent",
"backgroundPosition": "center",
"backgroundRepeat": "no-repeat",
"backgroundSize": "cover",
"borderBottomStyle": "solid",
"borderBottomWidth": "0px",
"borderLeftStyle": "solid",
"borderLeftWidth": "0px",
"borderRightStyle": "solid",
"borderRightWidth": "0px",
"borderTopStyle": "solid",
"borderTopWidth": "0px",
"boxSizing": "border-box",
"color": "inherit",
"display": null,
"flexBasis": "auto",
"flexDirection": "column",
"flexShrink": 0,
"font": "inherit",
"listStyle": "none",
"marginBottom": "0px",
"marginLeft": "0px",
"marginRight": "0px",
"marginTop": "0px",
"minHeight": "0px",
"minWidth": "0px",
"msFlexAlign": "stretch",
"msFlexDirection": "column",
"msFlexNegative": 0,
"msPreferredSize": "auto",
"paddingBottom": "0px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingTop": "0px",
"position": "relative",
"textAlign": "inherit",
"textDecoration": "none",
}
} />
`;
exports[`components/Image sets correct accessibility role" 1`] = `
<div
className=" __style_df"
role="img"
style={
Object {
"MozBoxSizing": "border-box",
"WebkitAlignItems": "stretch",
"WebkitBoxAlign": "stretch",
"WebkitBoxDirection": "normal",
"WebkitBoxOrient": "vertical",
"WebkitFlexBasis": "auto",
"WebkitFlexDirection": "column",
"WebkitFlexShrink": 0,
"alignItems": "stretch",
"backgroundColor": "transparent",
"backgroundPosition": "center",
"backgroundRepeat": "no-repeat",
"backgroundSize": "cover",
"borderBottomStyle": "solid",
"borderBottomWidth": "0px",
"borderLeftStyle": "solid",
"borderLeftWidth": "0px",
"borderRightStyle": "solid",
"borderRightWidth": "0px",
"borderTopStyle": "solid",
"borderTopWidth": "0px",
"boxSizing": "border-box",
"color": "inherit",
"display": null,
"flexBasis": "auto",
"flexDirection": "column",
"flexShrink": 0,
"font": "inherit",
"listStyle": "none",
"marginBottom": "0px",
"marginLeft": "0px",
"marginRight": "0px",
"marginTop": "0px",
"minHeight": "0px",
"minWidth": "0px",
"msFlexAlign": "stretch",
"msFlexDirection": "column",
"msFlexNegative": 0,
"msPreferredSize": "auto",
"paddingBottom": "0px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingTop": "0px",
"position": "relative",
"textAlign": "inherit",
"textDecoration": "none",
}
} />
`;

View File

@@ -1,162 +1,100 @@
/* eslint-env mocha */
/* eslint-env jasmine, jest */
import { mount, shallow } from 'enzyme'
import assert from 'assert'
import React from 'react'
import StyleSheet from '../../../apis/StyleSheet'
import Image from '../';
import React from 'react';
import renderer from 'react-test-renderer';
import Image from '../'
jest.mock('react-dom');
const originalImage = window.Image;
describe('components/Image', () => {
beforeEach(() => {
window.Image = jest.fn(() => ({}));
});
afterEach(() => {
window.Image = originalImage;
});
suite('components/Image', () => {
test('sets correct accessibility role"', () => {
const image = shallow(<Image />)
assert.equal(image.prop('accessibilityRole'), 'img')
})
const component = renderer.create(<Image />);
expect(component.toJSON()).toMatchSnapshot();
});
test('prop "accessibilityLabel"', () => {
const accessibilityLabel = 'accessibilityLabel'
const image = shallow(<Image accessibilityLabel={accessibilityLabel} />)
assert.equal(image.prop('accessibilityLabel'), accessibilityLabel)
})
const component = renderer.create(<Image accessibilityLabel='accessibilityLabel' />);
expect(component.toJSON()).toMatchSnapshot();
});
test('prop "accessible"', () => {
const accessible = false
const image = shallow(<Image accessible={accessible} />)
assert.equal(image.prop('accessible'), accessible)
})
const component = renderer.create(<Image accessible={false} />);
expect(component.toJSON()).toMatchSnapshot();
});
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 component = renderer.create(<Image children={children} />);
expect(component.toJSON()).toMatchSnapshot();
});
suite('prop "defaultSource"', () => {
describe('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 component = renderer.create(<Image defaultSource={defaultSource} />);
expect(component.toJSON()).toMatchSnapshot();
});
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 component = renderer.create(<Image defaultSource={defaultSource} />);
expect(component.toJSON()).toMatchSnapshot();
});
test('prop "onError"', function (done) {
this.timeout(5000)
mount(<Image onError={onError} source={{ uri: 'https://google.com/favicon.icox' }} />)
function onError(e) {
assert.equal(e.nativeEvent.type, 'error')
done()
}
})
test('sets "height" and "width" styles if missing', () => {
const defaultSource = { uri: 'https://google.com/favicon.ico', height: 10, width: 20 };
const component = renderer.create(<Image defaultSource={defaultSource} />);
expect(component.toJSON()).toMatchSnapshot();
});
test('prop "onLoad"', function (done) {
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()
}
})
test('does not override "height" and "width" styles', () => {
const defaultSource = { uri: 'https://google.com/favicon.ico', height: 10, width: 20 };
const component = renderer.create(<Image defaultSource={defaultSource} style={{ height: 20, width: 40 }} />);
expect(component.toJSON()).toMatchSnapshot();
});
});
test('prop "onLoadEnd"', function (done) {
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()
}
})
describe('prop "resizeMode"', () => {
[
Image.resizeMode.contain,
Image.resizeMode.cover,
Image.resizeMode.none,
Image.resizeMode.stretch,
undefined
].forEach((resizeMode) => {
test(`value "${resizeMode}"`, () => {
const component = renderer.create(<Image resizeMode={resizeMode} />);
expect(component.toJSON()).toMatchSnapshot();
});
});
});
test('prop "onLoadStart"', function (done) {
this.timeout(5000)
mount(<Image onLoadStart={onLoadStart} source={{ uri: 'https://google.com/favicon.ico' }} />)
function onLoadStart() {
assert.ok(true)
done()
}
})
suite('prop "resizeMode"', () => {
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')
})
test('value "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')
})
test('value "stretch"', () => {
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')
})
})
suite('prop "source"', function () {
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} />)
function onLoad(e) {
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} />)
function onLoad(e) {
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')
})
test('removes "resizeMode" property', () => {
const image = shallow(<Image style={{ resizeMode: Image.resizeMode.contain }} />)
assert.equal(StyleSheet.flatten(image.prop('style')).resizeMode, undefined)
})
})
describe('prop "style"', () => {
test('correctly supports "resizeMode" property', () => {
const component = renderer.create(<Image style={{ resizeMode: Image.resizeMode.contain }} />);
expect(component.toJSON()).toMatchSnapshot();
});
});
test('prop "testID"', () => {
const testID = 'testID'
const image = shallow(<Image testID={testID} />)
assert.equal(image.prop('testID'), testID)
})
})
const component = renderer.create(<Image testID='testID' />);
expect(component.toJSON()).toMatchSnapshot();
});
test('passes other props through to underlying View', () => {
const fn = () => {};
const component = renderer.create(<Image onResponderGrant={fn} />);
expect(component.toJSON()).toMatchSnapshot();
});
});

View File

@@ -1,33 +1,44 @@
/* 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 ImageResizeMode from './ImageResizeMode';
import ImageStylePropTypes from './ImageStylePropTypes';
import requestAnimationFrame from 'fbjs/lib/requestAnimationFrame';
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
height: PropTypes.number,
uri: PropTypes.string.isRequired,
width: PropTypes.number
}),
PropTypes.string
])
]);
const resolveAssetDimensions = (source) => {
if (typeof source === 'object') {
const { height, width } = source;
return { height, width };
}
};
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,
...View.propTypes,
children: PropTypes.any,
defaultSource: ImageSourcePropType,
onError: PropTypes.func,
@@ -35,51 +46,52 @@ 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 = {
accessible: true,
style: {}
};
static resizeMode = ImageResizeMode;
constructor(props, context) {
super(props, context)
const uri = resolveAssetSource(props.source)
this._imageState = uri ? STATUS_PENDING : STATUS_IDLE
this.state = { isLoaded: false }
super(props, context);
this.state = { isLoaded: false };
const uri = resolveAssetSource(props.source);
this._imageState = uri ? STATUS_PENDING : STATUS_IDLE;
this._isMounted = false;
}
componentDidMount() {
if (this._imageState === STATUS_PENDING) {
this._createImageLoader()
this._createImageLoader();
}
this._isMounted = true;
}
componentDidUpdate() {
if (this._imageState === STATUS_PENDING && !this.image) {
this._createImageLoader()
this._createImageLoader();
}
}
componentWillReceiveProps(nextProps) {
const nextUri = resolveAssetSource(nextProps.source)
const nextUri = resolveAssetSource(nextProps.source);
if (resolveAssetSource(this.props.source) !== nextUri) {
this._updateImageState(nextUri ? STATUS_PENDING : STATUS_IDLE)
this._updateImageState(nextUri ? STATUS_PENDING : STATUS_IDLE);
}
}
componentWillUnmount() {
this._destroyImageLoader()
this._destroyImageLoader();
this._isMounted = false;
}
render() {
const { isLoaded } = this.state
const { isLoaded } = this.state;
const {
accessibilityLabel,
accessible,
@@ -87,132 +99,118 @@ class Image extends Component {
defaultSource,
onLayout,
source,
testID
} = this.props
testID,
/* eslint-disable */
resizeMode,
/* eslint-enable */
...other
} = this.props;
const displayImage = resolveAssetSource(!isLoaded ? defaultSource : source)
const backgroundImage = displayImage ? `url("${displayImage}")` : null
let style = StyleSheet.flatten(this.props.style)
const displayImage = resolveAssetSource(!isLoaded ? defaultSource : source);
const imageSizeStyle = resolveAssetDimensions(!isLoaded ? defaultSource : source);
const backgroundImage = displayImage ? `url("${displayImage}")` : null;
const originalStyle = StyleSheet.flatten(this.props.style);
const finalResizeMode = resizeMode || originalStyle.resizeMode || ImageResizeMode.cover;
const resizeMode = this.props.resizeMode || style.resizeMode || ImageResizeMode.cover
// remove 'resizeMode' style, as it is not supported by View (N.B. styles are frozen in dev)
style = process.env.NODE_ENV !== 'production' ? { ...style } : style
delete style.resizeMode
const style = StyleSheet.flatten([
styles.initial,
imageSizeStyle,
originalStyle,
backgroundImage && { backgroundImage },
resizeModeStyles[finalResizeMode]
]);
// View doesn't support 'resizeMode' as a style
delete style.resizeMode;
/**
* Image is a non-stretching View. The image is displayed as a background
* image to support `resizeMode`. The HTML image is hidden but used to
* provide the correct responsive image dimensions, and to support the
* image context menu. Child content is rendered into an element absolutely
* positioned over the image.
*/
return (
<View
{...other}
accessibilityLabel={accessibilityLabel}
accessibilityRole='img'
accessible={accessible}
children={children}
onLayout={onLayout}
ref='root'
style={[
styles.initial,
style,
backgroundImage && { backgroundImage },
resizeModeStyles[resizeMode]
]}
style={style}
testID={testID}
>
{createReactDOMComponent({ component: '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 }
this._destroyImageLoader()
this._updateImageState(STATUS_ERRORED)
this._onLoadEnd()
if (onError) onError(event)
_onError = () => {
const { onError, source } = this.props;
this._destroyImageLoader();
this._onLoadEnd();
this._updateImageState(STATUS_ERRORED);
if (onError) {
onError({
nativeEvent: {
error: `Failed to load resource ${resolveAssetSource(source)} (404)`
}
});
}
}
_onLoad = (e) => {
const { onLoad } = this.props
const event = { nativeEvent: e }
const { onLoad } = this.props;
const event = { nativeEvent: e };
this._destroyImageLoader()
this._updateImageState(STATUS_LOADED)
if (onLoad) onLoad(event)
this._onLoadEnd()
this._destroyImageLoader();
this._updateImageState(STATUS_LOADED);
if (onLoad) { onLoad(event); }
this._onLoadEnd();
}
_onLoadEnd() {
const { onLoadEnd } = this.props
if (onLoadEnd) onLoadEnd()
const { onLoadEnd } = this.props;
if (onLoadEnd) { onLoadEnd(); }
}
_onLoadStart() {
const { onLoadStart } = this.props
this._updateImageState(STATUS_LOADING)
if (onLoadStart) onLoadStart()
const { onLoadStart } = this.props;
this._updateImageState(STATUS_LOADING);
if (onLoadStart) { onLoadStart(); }
}
_updateImageState(status) {
this._imageState = status
const isLoaded = this._imageState === STATUS_LOADED
this._imageState = status;
const isLoaded = this._imageState === STATUS_LOADED;
if (isLoaded !== this.state.isLoaded) {
this.setState({ isLoaded })
requestAnimationFrame(() => {
if (this._isMounted) {
this.setState({ isLoaded });
}
});
}
}
}
applyNativeMethods(Image)
const styles = StyleSheet.create({
initial: {
alignSelf: 'flex-start',
backgroundColor: 'transparent',
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat',
backgroundSize: 'cover'
},
img: {
borderWidth: 0,
height: 'auto',
maxHeight: '100%',
maxWidth: '100%',
opacity: 0
},
children: {
bottom: 0,
left: 0,
position: 'absolute',
right: 0,
top: 0
}
})
});
const resizeModeStyles = StyleSheet.create({
center: {
@@ -235,6 +233,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 */
/* eslint-env jasmine, jest */
suite('components/ListView', () => {
test('NO TEST COVERAGE')
})
describe('components/ListView', () => {
test('NO TEST COVERAGE');
});

View File

@@ -1,12 +1,9 @@
import applyNativeMethods from '../../modules/applyNativeMethods'
import React, { Component } from 'react'
import ScrollView from '../ScrollView'
import ListViewDataSource from './ListViewDataSource'
import ListViewPropTypes from './ListViewPropTypes'
import View from '../View'
import pick from 'lodash/pick'
const SCROLLVIEW_REF = 'listviewscroll'
import applyNativeMethods from '../../modules/applyNativeMethods';
import ListViewDataSource from './ListViewDataSource';
import ListViewPropTypes from './ListViewPropTypes';
import ScrollView from '../ScrollView';
import View from '../View';
import React, { Component } from 'react';
class ListView extends Component {
static propTypes = ListViewPropTypes;
@@ -23,81 +20,81 @@ 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)
return React.cloneElement(this.props.renderScrollComponent(this.props), {
ref: this._setScrollViewRef
}, header, children, footer);
}
return React.cloneElement(this.props.renderScrollComponent(props), {
ref: SCROLLVIEW_REF
}, header, children, footer)
_setScrollViewRef = (component) => {
this._scrollViewRef = component;
}
}
applyNativeMethods(ListView)
module.exports = ListView
module.exports = applyNativeMethods(ListView);

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