Compare commits

...

160 Commits

Author SHA1 Message Date
Nicolas Gallagher
54597edbaf 0.0.18 2016-03-15 17:27:42 -07:00
Nicolas Gallagher
fc31287566 [fix] remove -webkit-tap-highlight-color 2016-03-15 17:26:29 -07:00
Nicolas Gallagher
21cc8f47ba Update documentation 2016-03-15 17:14:44 -07:00
Nicolas Gallagher
bf7beb4102 Update dependencies 2016-03-15 14:25:27 -07:00
Nicolas Gallagher
127d103c0a Fix lint issues 2016-03-15 14:19:46 -07:00
Nicolas Gallagher
ae6132af56 Update library exports 2016-03-15 14:19:29 -07:00
Nicolas Gallagher
3c4d7655db [fix] Touchable: adapt RN touchables for Web 2016-03-15 14:18:08 -07:00
Nicolas Gallagher
190966f411 [fix] ScrollView: based on ScrollResponder 2016-03-15 14:07:45 -07:00
Nicolas Gallagher
8d5ecb84d5 [add] ScrollResponder: adjust to work on Web 2016-03-15 13:49:03 -07:00
Nicolas Gallagher
b4a9177ce3 [add] dismissKeyboard module 2016-03-15 13:44:50 -07:00
Nicolas Gallagher
ad4a6c5be7 [fix] View: guard against browser flexbox bugs
There are various flexbox bugs that can cause views to grow beyond the
vertical or horizontal bounds of their container. The width and height
styles avoid most of them manifesting.
2016-03-15 13:43:25 -07:00
Nicolas Gallagher
5f795dfc6c Clean up View's event normalization code 2016-03-15 13:43:11 -07:00
Nicolas Gallagher
949cb75894 [add] TextInput: 'clear' method on instance 2016-03-15 13:38:54 -07:00
Nicolas Gallagher
2e1914080f [add] TextInputState 2016-03-15 13:38:28 -07:00
Nicolas Gallagher
49e9e0ab5b [add] StyleSheet: new prop types 2016-03-15 13:32:52 -07:00
Nicolas Gallagher
ee4c544957 [fix] StyleSheet: support for 'transform' and 'transformMatrix'
Fix #46
2016-03-15 13:32:02 -07:00
Nicolas Gallagher
56549cf794 [add] NativeMethodsMixin: 'measureInWindow' support 2016-03-15 13:24:36 -07:00
Nicolas Gallagher
e6811b2134 [fix] ResponderEventPlugin: touch or mouse dependencies
**Problem**

Browsers tend to fire both touch and mouse events when touch is
supported. This causes touchables to fire responder callbacks twice.
For example, 'TouchableOpacity' will animate the opacity twice in
response to a touch.

**Response**

If a browser supports touch, don't include the mouse events as
dependencies of the responder events. This is not ideal, as some devices
support both touch and mouse interactions. This will need to be
revisited.
2016-03-15 13:19:43 -07:00
Nicolas Gallagher
d8b7dcc60f Add 'locationX/Y' to recorded Responder events 2016-03-15 13:19:24 -07:00
Nicolas Gallagher
62a08f09ab [fix] ResponderEventPlugin: add missing 'responderRelease' dependencies 2016-03-15 13:17:25 -07:00
Nicolas Gallagher
3e7cd1a001 [add] UIManager: 'blur', 'focus' and 'measureInWindow' 2016-03-15 13:14:06 -07:00
Nicolas Gallagher
8441755d61 [fix] 'window' value in 'Dimension' 2016-03-15 11:35:36 -07:00
Nicolas Gallagher
ba9fa2a7a0 [add] upstream 'merge' module 2016-03-15 11:34:09 -07:00
Nicolas Gallagher
e26edfb9ea Move NativeMethodsDecorator 2016-03-14 23:25:58 -07:00
Nicolas Gallagher
9a8a9ad209 Use 'module.exports' over 'export default'
The use of CommonJS require in RN modules makes it simpler to use
CommonJS exports everywhere.
2016-03-14 23:21:12 -07:00
Nicolas Gallagher
efccbe41bb 0.0.17 2016-03-12 08:00:02 -08:00
IjzerenHein
f6f8d30aba [fix] PanResponder improvements + mouse support
- Adds `locationX` and `locationY` to touch events
- Adds `timestamp` to the `touches` and `touchesChanged` data
- Add mouse event support

Close #94
2016-03-12 07:53:24 -08:00
Nicolas Gallagher
6d7d98c149 [add] support directly requiring image assets
Thanks to IjzerenHein <hrutjes@gmail.com>. See #84
2016-03-08 18:03:45 -08:00
IjzerenHein
77d201988d Add .editorconfig 2016-03-08 09:40:28 -08:00
Nicolas Gallagher
b3d7332ddd Update 'known issues' guide 2016-03-08 09:33:56 -08:00
IjzerenHein
651d519500 [add] NativeModules shim
The `NativeModules` namespace is import by some libraries, which will
throw an error if missing.

Close #86
2016-03-08 09:32:14 -08:00
Hein Rutjes
06d8614519 Merge pull request #1 from necolas/master
Update from original master
2016-03-08 10:57:42 +01:00
Nicolas Gallagher
3eced7e842 Update documentation 2016-03-08 00:52:13 -08:00
rofrischmann
e627e0cd77 Use inline-style-prefix-all for vendor prefixes
Fix #74
Close #82
2016-03-08 00:31:43 -08:00
Nicolas Gallagher
7ab33727c4 Minor test clean up 2016-03-08 00:21:47 -08:00
Nicolas Gallagher
7295a8fee8 Add tests for Image resizeMode 2016-03-08 00:21:35 -08:00
IjzerenHein
9a09456532 [add] Image: support resizeMode style 2016-03-08 00:19:56 -08:00
Nicolas Gallagher
4cd38552cd Fix the 'test:watch' npm-script 2016-03-07 21:18:30 -08:00
Neil Kistner
b0f35f6c66 Add syntax highlighting to docs where missing 2016-03-07 20:43:56 -08:00
Nicolas Gallagher
2dff45b561 Add link to Game2048 example 2016-03-07 20:43:22 -08:00
Nicolas Gallagher
fd9232201d 0.0.16 2016-02-22 19:39:12 -08:00
Nicolas Gallagher
29f6bd363c Fix linting error and check-in test patch 2016-02-22 19:23:05 -08:00
Nicolas Gallagher
4845de5cb5 [add] Update components with native methods
Hack in touch event normalization within `View` to produce events that
contain `pageX`, `pageY` for React Native compatibility.
2016-02-22 16:41:50 -08:00
Nicolas Gallagher
267a9b55bf [add] inject ResponderEventPlugin 2016-02-22 16:40:18 -08:00
Nicolas Gallagher
7add5c524a [add] mirror various React Native APIs
Copy over API code that requires no DOM modifications:

- Animated
- Easing
- Interaction Manager (stub)
- PanResponder
- Touchable
2016-02-22 16:37:42 -08:00
Nicolas Gallagher
8e0d94e092 Use 'fbjs' instead of separate packages 2016-02-22 14:00:43 -08:00
Nicolas Gallagher
25f96ba8ae [fix] StyleSheet prefixing on client and server
A small error from referencing the wrong value caused prefixed values to
be dropped. The patch also updated inline-style-prefixer and turns all
vendor prefixes on by default. This provides the option to drop all the
caniuse and bowser data that inline-style-prefixer uses (probably by
forking the project and removing those dependencies).

Fix #51
2016-02-22 13:55:09 -08:00
Nicolas Gallagher
2b90bd736f Minor docs update 2016-02-19 13:17:39 -08:00
Nicolas Gallagher
791ede06dd 0.0.15 2016-02-18 23:04:35 -08:00
Nicolas Gallagher
0567232942 [fix] NetInfo export 2016-02-18 23:03:59 -08:00
Nicolas Gallagher
e5ecc26d21 0.0.14 2016-02-18 21:32:08 -08:00
Nicolas Gallagher
715c71b215 Refactor StyleSheet to support arrays 2016-02-18 16:45:23 -08:00
Nicolas Gallagher
f8554ecc1e Update README; add guides to docs 2016-02-17 00:49:18 -08:00
Nicolas Gallagher
3292ced765 Add new components
- ActivityIndicator
- Portal
- StaticContainer
- StaticRenderer
2016-02-17 00:20:02 -08:00
Nicolas Gallagher
1c7fb4cb45 Add APIs
- AppRegistry
- AppState
- AsyncStorage
- Dimensions
- NativeMethods
- NetInfo
- PixelRatio
- Platform
- UIManager
2016-02-16 23:54:06 -08:00
Nicolas Gallagher
60409bea18 BSD License; CONTRIBUTING.md 2016-02-16 23:43:41 -08:00
Nicolas Gallagher
5c74c0efb7 Move StyleSheet into 'apis' directory 2016-02-16 23:40:50 -08:00
Ben Alpert
a0187f9b1a Add semicolons to property initializers
According to the current proposal, semicolons are required. Babel 6.4
was updated to require them (see github.com/babel/babel/pull/3225). This
is required in order to fix the build -- master will be red on Travis as
soon as the build is rerun because it will pick up the latest Babel
parser.
2016-01-29 10:16:58 -08:00
Nicolas Gallagher
74ef265d83 [chore] update documentation 2015-12-30 14:10:22 -08:00
Nicolas Gallagher
97b3a91c0e [add] StyleSheet: immutable style in development
Fix #66
2015-12-30 14:10:15 -08:00
Nicolas Gallagher
c65aa8a943 0.0.13 2015-12-27 12:05:35 +00:00
Nicolas Gallagher
0aa60a3c29 [fix] umd bundle 2015-12-27 12:04:40 +00:00
Nicolas Gallagher
8ac84f6da5 [change] StyleSheet: support code-splitting / export smaller API
Quick-fix for code-splitting support by updating the rendered style
sheet in place. Reduce the API to `create`, as the rest is now internal
to the framework.

Fix #34
2015-12-27 11:54:53 +00:00
Nicolas Gallagher
69166b1502 [fix] StyleSheet: support textAlign 'inherit' 2015-12-27 11:46:03 +00:00
Nicolas Gallagher
cc10de43eb [change] export or replace react-dom methods
This change adds the react-dom methods to the main export, since this is
a Web-only environment (React Native does something similar). It
augments the default render methods in order to move style sheet
management under the control of the library (necessary for
code-splitting support).

Relates to #52
2015-12-27 11:45:49 +00:00
Nicolas Gallagher
c850a5fa8c [add] CSS textShadow and reintroduce enums 2015-12-26 17:54:56 +00:00
Nicolas Gallagher
1efe5a533f [add] StyleSheet: support 'flex' style prop
Fix #63
2015-12-26 17:54:13 +00:00
Nicolas Gallagher
804132ce36 [fix] 'process.env.NODE_ENV' check
Use babel to transpile the source code without bundling it.
Use webpack to create a standalone, productionized UMD bundle.

Fix #50
2015-12-26 14:22:36 +00:00
Nicolas Gallagher
5335bcfd48 [chore] docs and formatting 2015-12-26 10:40:36 +00:00
Nicolas Gallagher
c0e7afc495 [change] Touchable: default prop values from RN Touchables 2015-12-22 00:15:48 +00:00
Nicolas Gallagher
fa88548c3c Update CONTRIBUTING and README with CodePen link 2015-12-21 23:50:58 +00:00
Nicolas Gallagher
39b2b2f979 Fix unreliable TextInput tests 2015-12-20 04:26:59 -08:00
Nicolas Gallagher
fd04d65b03 0.0.12 2015-12-20 03:24:57 -08:00
Nicolas Gallagher
0ab984f507 [change] TextInput: implement placeholder and placeholderTextColor
Without access to the Shadow DOM pseudo-elements, the placeholder
behaviour needs to be reimplemented.

Update to match React Native's modification to `TextInput` to include
all `View` props and use the `Text` style props.

Fix #12
Fix #48
2015-12-20 03:11:39 -08:00
Nicolas Gallagher
3d1ad50a58 Update documentation and examples 2015-12-19 10:59:22 -08:00
Nicolas Gallagher
92554321df [add] Text: support all ViewStylePropTypes 2015-12-19 10:51:29 -08:00
Nicolas Gallagher
1c9270c4ea [fix] ReactNative export pattern 2015-12-19 10:49:49 -08:00
Nicolas Gallagher
8a5f9cd7d9 0.0.11 2015-12-19 06:04:03 -08:00
Nicolas Gallagher
aac6b796b2 Replace 'EnvironmentPlugin' with 'DefinePlugin' 2015-12-19 05:47:20 -08:00
Nicolas Gallagher
c77ce19f1b [fix] StyleSheet: escaping of class names in CSS
Fix #47
2015-12-19 04:22:00 -08:00
Nicolas Gallagher
25b74d30c4 [fix] Text: style props 2015-12-19 03:57:04 -08:00
Nicolas Gallagher
4191d58694 Fix styles in 'GridView' example 2015-12-19 03:27:44 -08:00
Nicolas Gallagher
11b861ae64 [add] support for 'list' and 'listitem' accessibilityRole mapping
Fix #49
2015-12-19 03:25:40 -08:00
Nicolas Gallagher
68bf08112a [fix] StylePropTypes: add support for 'verticalAlign'
Fix #45
2015-12-19 03:11:55 -08:00
Nicolas Gallagher
b277b3e509 [fix] StylePropTypes: add support for 'boxShadow'
Fix #44
2015-12-19 03:11:12 -08:00
Nicolas Gallagher
c135dddbd1 [fix] StyleSheet: isStyleObject check 2015-12-14 15:28:40 -08:00
Nicolas Gallagher
ffc6368162 0.0.10 2015-12-13 12:49:48 -08:00
Nicolas Gallagher
501c19fe9b [fix] StyleSheet: shorthand properties
Expand shorthand properties to preserve the one-rule-to-one-style
isolation. Resolve styles like React Native - most specific comes last.
Add support for the hz and vt properties for margin and padding.

Fix #40
2015-12-13 12:48:26 -08:00
Kirill Korolyov
e1da11fa1d [change] transparent default Image backgroundColor 2015-12-07 13:33:16 -08:00
Nicolas Gallagher
b2a4d742a9 [chore] update dependencies 2015-12-01 16:01:51 -08:00
Nicolas Gallagher
8b965fdfa0 [chore] update development dependencies 2015-12-01 15:58:04 -08:00
Nicolas Gallagher
8cfef85934 [fix] Image tests 2015-12-01 15:50:38 -08:00
Nicolas Gallagher
6db24e9358 [fix] Image: default 'resizeMode' is 'cover'
Fix #41
2015-12-01 14:44:17 -08:00
Nicolas Gallagher
13e36bee65 0.0.9 2015-10-24 12:06:37 -07:00
Nicolas Gallagher
93e8e90a1a [fix] README docs 2015-10-24 11:54:31 -07:00
Tom Ashworth
894fd0362d [add] initial ScrollView
Supports the following props: `children`, `contentContainerStyle`,
`horizontal`, `onScroll`, `scrollEnabled`, `scrollEventThrottle`, and
`style`.

Fix #6
2015-10-24 11:35:30 -07:00
Nicolas Gallagher
a1664927ce [change] initial example with media queries 2015-10-21 18:00:10 -07:00
Nicolas Gallagher
ae2abc578a [change] update dependencies 2015-10-19 18:45:33 -07:00
Nicolas Gallagher
5f7b3f5fef [change] move examples 2015-10-19 18:32:38 -07:00
Nicolas Gallagher
75f653818a [change] move tests.webpack.js 2015-10-19 18:31:27 -07:00
Nicolas Gallagher
2c52d41b75 [fix] -ms- CSS vendor prefix 2015-10-19 16:02:41 -07:00
Nicolas Gallagher
83f749d983 [change] docs on MQs and a11y 2015-10-19 10:35:42 -07:00
Nicolas Gallagher
bf5046415c [fix] doc examples 2015-10-19 09:42:30 -07:00
Nicolas Gallagher
885d4586a9 [change] obfuscated selector prefix 2015-10-18 22:15:03 -07:00
Nicolas Gallagher
ea0a778ba3 [change] add more about accessibility to docs 2015-10-18 22:11:20 -07:00
Nicolas Gallagher
0a7eda2505 [fix] remove default link styles 2015-10-18 22:09:27 -07:00
Nicolas Gallagher
35385e7b69 [change] pointerEvents CSS 2015-10-18 22:08:36 -07:00
Nicolas Gallagher
3fd29697c0 [fix] initial 'display' value of 'Text'
Using `inline-block` breaks text overflow truncation in nested `Text`
components.

Fix gh-19
2015-10-18 18:08:39 -07:00
Nicolas Gallagher
a26033be2d 0.0.8 2015-10-18 17:56:05 -07:00
Nicolas Gallagher
fdb4ee4aae [change] StyleSheet docs 2015-10-18 17:49:35 -07:00
Nicolas Gallagher
08300f624f [change] remove 'component' prop; accessibility docs
- infer underlying HTML tag from 'accessibilityRole'
- move accessibility props to 'CoreComponent'
- remove the 'component' prop from exported Components

Fix gh-23
2015-10-18 15:24:59 -07:00
Nicolas Gallagher
7f5a2807e2 [fix] avoid eslint-plugin-react@3.6.0 bug 2015-10-18 11:23:29 -07:00
Nicolas Gallagher
292f045c52 [fix] README examples 2015-10-18 11:02:36 -07:00
Nicolas Gallagher
a19b57df4d 0.0.7 2015-10-18 09:11:38 -07:00
Nicolas Gallagher
1c444569ae [change] React 0.14 support 2015-10-18 08:54:09 -07:00
Nicolas Gallagher
0b8c4b8746 [fix] link to API docs 2015-10-17 18:51:01 -07:00
Nicolas Gallagher
6772233837 [remove] Swipeable 2015-10-17 18:49:21 -07:00
Nicolas Gallagher
cd89f88d96 [add] StyleSheet API
Initial StyleSheet implementation for Web. Converts style object
declarations to "atomic" CSS rules.

Close gh-25
2015-10-17 17:52:01 -07:00
Nicolas Gallagher
b59bdb17b2 [change] Unit test setup and reporter 2015-10-17 17:51:54 -07:00
Nicolas Gallagher
2bfa579fe4 [remove] babel-plugin-typecheck 2015-10-17 17:51:30 -07:00
Nicolas Gallagher
f4c6e33b2f Fix #26 2015-10-01 15:18:28 -07:00
Nicolas Gallagher
39b273f9d8 Moves 2015-09-20 21:00:15 -07:00
Nicolas Gallagher
ec9985a3b3 0.0.6 2015-09-20 19:52:18 -07:00
Nicolas Gallagher
0aa29d8816 Image: add test for 'role=img' 2015-09-20 19:41:13 -07:00
Nicolas Gallagher
45777e0405 Add '_className' escape-hatch to Text and View 2015-09-20 19:31:59 -07:00
Nicolas Gallagher
84e06564d4 Accessibility fixes for Image and Text 2015-09-20 19:21:01 -07:00
Nicolas Gallagher
1d1b633317 npm-script: fix 'test' 2015-09-20 16:40:46 -07:00
Nicolas Gallagher
565ec2fee8 Docs: minor update 2015-09-20 15:50:22 -07:00
Nicolas Gallagher
33082f988e Text: add props 'accessibilityLabel' and 'accessible'
These properties are missing in React Native but important for
accessible web interfaces.
2015-09-20 15:48:09 -07:00
Nicolas Gallagher
a2fb65a79c Touchable: activeHighlight -> activeUnderlayColor 2015-09-20 15:47:35 -07:00
Nicolas Gallagher
e727193809 TextInput: props and tests 2015-09-20 15:43:52 -07:00
Nicolas Gallagher
d6db206ec4 Fix type of 'fontSize' style 2015-09-20 15:29:00 -07:00
Nicolas Gallagher
b3beea9bb3 Fix npm scripts 2015-09-20 15:25:03 -07:00
Nicolas Gallagher
ef4de789ae ScrollView: initial docs 2015-09-14 17:28:20 -07:00
Nicolas Gallagher
86037ab740 Add links to slack and gitter rooms 2015-09-14 11:51:44 -07:00
Nicolas Gallagher
6c20646f10 Touchable: add 'accessibilityRole' prop 2015-09-13 22:59:45 -07:00
Nicolas Gallagher
b5aa68dc7a specHelpers: accept props 2015-09-13 22:59:04 -07:00
Nicolas Gallagher
5668c40ee6 Don't strip testID's in production 2015-09-13 21:43:25 -07:00
Nicolas Gallagher
be86250ac6 Image: fix example code 2015-09-12 18:08:58 -07:00
Nicolas Gallagher
1e04dfc306 filterObjectProps: props -> propKeys 2015-09-12 18:08:21 -07:00
Nicolas Gallagher
283ab2fa2e Update dependencies 2015-09-12 18:07:22 -07:00
Nicolas Gallagher
09dd9a224a Remove type tests; fix code style 2015-09-11 21:28:03 -07:00
Nicolas Gallagher
c7524b7b6f Add Flow type checking; React >= 0.13 2015-09-11 21:17:48 -07:00
Nicolas Gallagher
5453c8843a Trivial edits 2015-09-09 00:48:18 -07:00
Nicolas Gallagher
e0f836ccb5 Minor example page update 2015-09-09 00:31:32 -07:00
Nicolas Gallagher
eada8e7fc7 Refactor dev workflow 2015-09-08 23:29:31 -07:00
Nicolas Gallagher
114fb5f8c7 LICENCE -> LICENSE 2015-09-08 22:44:27 -07:00
Nicolas Gallagher
4aa87c79fa Fix tests and code style 2015-09-08 14:21:17 -07:00
Nicolas Gallagher
9107fd3de9 Use more reliable npm badge host 2015-09-08 09:26:16 -07:00
Nicolas Gallagher
c72173ff88 Image: set 'resizeMode' default to 'stretch'
Fix #8
2015-09-08 00:29:53 -07:00
Nicolas Gallagher
edf0fda75a Docs: minor adjustments 2015-09-08 00:24:12 -07:00
Nicolas Gallagher
3b848fe378 View: additional accessibility props
* Add `accessibilityLiveRegion` for `aria-live` support.
* Add `accessibilityRole` for `role` support.

Fix #11
2015-09-08 00:09:09 -07:00
Nicolas Gallagher
7eff1a644e Spec helpers 2015-09-07 23:37:36 -07:00
Nicolas Gallagher
2750d70a93 Add support for 'accessible' prop 2015-09-07 22:09:16 -07:00
Nicolas Gallagher
65559f50e6 Image: fix ARIA role 2015-09-07 19:16:58 -07:00
Nicolas Gallagher
77fd21ea44 Touchable: add support for 'style' and keyboard 2015-09-07 14:40:37 -07:00
Nicolas Gallagher
38e4de76cd Documentation edits; transfer props 2015-09-07 13:14:09 -07:00
Nicolas Gallagher
0f42cd83e1 Component stylePropTypes keys as constants 2015-09-07 13:00:59 -07:00
Nicolas Gallagher
e6cbea82c4 Don't optimize published package 2015-09-07 12:59:16 -07:00
Nicolas Gallagher
6d4c9e881f Move CoreComponent module 2015-09-07 12:58:48 -07:00
170 changed files with 12269 additions and 2799 deletions

View File

@@ -1,7 +1,10 @@
{
"optional": [
"es7.classProperties",
"runtime"
"presets": [
"es2015",
"stage-1",
"react"
],
"stage": 1
"plugins": [
"transform-decorators-legacy"
]
}

9
.editorconfig Normal file
View File

@@ -0,0 +1,9 @@
# EditorConfig: http://editorconfig.org
root = true
[*]
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2

View File

@@ -1,2 +0,0 @@
dist
docs

View File

@@ -3,9 +3,6 @@
"parser": "babel-eslint",
// based on https://github.com/feross/standard
"extends": [ "standard", "standard-react" ],
"env": {
"mocha": true
},
"rules": {
// overrides of the standard style
"space-before-function-paren": [ 2, { "anonymous": "always", "named": "never" } ],

View File

@@ -1,6 +1,10 @@
language: node_js
node_js:
- "0.12"
- "5"
- "4"
before_script:
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
script:
- npm run lint
- npm test

View File

@@ -1,125 +1,78 @@
# Contributing to this project
# Contributing
The issue tracker is the preferred channel for [bug reports](#bugs),
[features requests](#features) and [submitting pull
requests](#pull-requests).
We are open to, and grateful for, any contributions made by the community.
<a name="bugs"></a>
## Bug reports
## Reporting Issues and Asking Questions
A bug is a _demonstrable problem_ that is caused by the code in the repository.
Good bug reports are extremely helpful - thank you!
Before opening an issue, please search the [issue
tracker](https://github.com/necolas/react-native-web/issues) to make sure your
issue hasn't already been reported.
Guidelines for bug reports:
## Development
1. **Use the GitHub issue search** &mdash; check if the issue has already been
reported.
Visit the [Issue tracker](https://github.com/necolas/react-native-web/issues)
to find a list of open issues that need attention.
2. **Check if the issue has been fixed** &mdash; try to reproduce it using the
latest `master` or development branch in the repository.
Fork, then clone the repo:
3. **Isolate the problem** &mdash; create a [reduced test
case](http://css-tricks.com/reduced-test-cases/) and a live example.
```
git clone https://github.com/your-username/react-native-web.git
```
A good bug report contains as much detail as possible. What is your
environment? What steps will reproduce the issue? What browser(s) and OS
experience the problem? What would you expect to be the outcome? All these
details really help!
Run the examples:
Example:
```
npm run examples
```
> Short and descriptive example bug report title
>
> A summary of the issue and the browser/OS environment in which it occurs. If
> suitable, include the steps required to reproduce the bug.
>
> 1. This is the first step
> 2. This is the second step
> 3. Further steps, etc.
>
> `<url>` - a link to the reduced test case
>
> Any other information you want to share that is relevant to the issue being
> reported. This might include the lines of code that you have identified as
> causing the bug, and potential solutions (and your opinions on their
> merits).
### Building
```
npm run build
```
<a name="features"></a>
## Feature requests
To create a UMD build:
Feature requests are welcome. But take a moment to find out whether your idea
fits with the scope and aims of the project. It's up to *you* to make a strong
case to convince the project's developers of the merits of this feature. Please
provide as much detail and context as possible.
```
npm run build:umd
```
### Testing and Linting
<a name="pull-requests"></a>
## Pull requests
To run the tests:
Good pull requests - patches, improvements, new features - are a fantastic
help. Please keep them focused in scope and avoid containing unrelated commits.
```
npm run test
```
**Please ask first** before embarking on any significant pull request (e.g.
implementing new features or components, refactoring code), otherwise you risk
spending a lot of time working on something that the project's developers might
not want to merge into the project.
To continuously watch and run tests, run the following:
Development commands:
```
npm run test:watch
```
* `npm start` start the dev server and develop against live examples
* `npm run lint` run the linter
* `npm run specs` run the unit tests
* `npm run build` generate a build
To perform linting, run the following:
Please follow this process for submitting a patch:
```
npm run lint
```
1. [Fork](http://help.github.com/fork-a-repo/) the project, clone your fork,
and configure the remotes:
### New Features
```bash
# Clone your fork of the repo into the current directory
git clone https://github.com/<your-username>/react-native-web
# Navigate to the newly cloned directory
cd react-native-web
# Assign the original repo to a remote called "upstream"
git remote add upstream https://github.com/necolas/react-native-web
```
Please open an issue with a proposal for a new feature or refactoring before
starting on the work. We don't want you to waste your efforts on a pull request
that we won't want to accept.
2. If you cloned a while ago, get the latest changes from upstream:
## Submitting Changes
```bash
git checkout master
git pull upstream master
```
* Open a new issue in the [Issue tracker](https://github.com/necolas/react-native-web/issues).
* Fork the repo.
* Create a new feature branch based off the `master` branch.
* Make sure all tests pass and there are no linting errors.
* Submit a pull request, referencing any issues it addresses.
3. Create a new topic branch (off the main project development branch) to
contain your feature, change, or fix:
Please try to keep your pull request focused in scope and avoid including unrelated commits.
```bash
git checkout -b <topic-branch-name>
```
After you have submitted your pull request, we'll try to get back to you as soon as possible. We may suggest some changes or improvements.
4. Commit your changes in logical chunks. Please adhere to these [git commit
message guidelines](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
or your code is unlikely be merged into the main project. Use Git's
[interactive rebase](https://help.github.com/articles/interactive-rebase)
feature to tidy up your commits before making them public.
5. Locally merge (or rebase) the upstream development branch into your topic branch:
```bash
git pull [--rebase] upstream master
```
6. Push your topic branch up to your fork:
```bash
git push origin <topic-branch-name>
```
7. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/)
with a clear title and description.
**IMPORTANT**: By submitting a patch, you agree to allow the project owner to
license your work under the same license as that used by the project.
Thank you for contributing!

21
LICENCE
View File

@@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2015 Nicolas Gallagher
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

31
LICENSE Normal file
View File

@@ -0,0 +1,31 @@
BSD License
For React Native software
Copyright (c) 2015-present, Nicolas Gallagher. All rights reserved.
Copyright (c) 2015-present, Facebook, Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name Facebook nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

244
README.md
View File

@@ -2,188 +2,128 @@
[![Build Status][travis-image]][travis-url]
[![npm version][npm-image]][npm-url]
![gzipped size](https://img.shields.io/badge/gzipped-~41.0k-blue.svg)
The core [React Native][react-native-url] components adapted and expanded upon
for the web, backed by a precomputed CSS library. ~19KB minified and gzipped.
[React Native][react-native-url] components and APIs for the Web.
## Table of contents
Browser support: Chrome, Firefox, Safari >= 7, IE 10, Edge.
* [Install](#install)
* [Use](#use)
* [Components](#components)
* [Styling](#styling)
* [Contributing](#contributing)
* [Thanks](#thanks)
* [License](#license)
## Overview
## Install
"React Native for Web" is a project to bring React Native's building blocks and
touch handling to the Web.
React Native provides a foundational layer to support interoperable,
zero-configuration React component development. This is missing from React's
web ecosystem where OSS components rely on inline styles (usually without
vendor prefixes), or require build tool configuration. This project allows
components built upon React Native to be run on the Web, and it manages all
component styling out-of-the-box.
For example, the [`View`](docs/apis/View.md) component makes it easy to build
cross-browser layouts with flexbox, such as stacked and nested boxes with
margin and padding. And the [`StyleSheet`](docs/guides/style.md) API converts
styles defined in JavaScript into "Atomic CSS".
## Quick start
To install in your app:
```
npm install --save react react-native-web
npm install --save react@0.14 react-dom@0.14 react-native-web
```
## Use
Read the [Client and Server rendering](docs/guides/rendering.md) guide.
React Native for Web exports its components and a reference to the `React`
installation. Styles are authored in JavaScript as plain objects.
## Examples
Demos:
* [React Native for Web: Playground](http://codepen.io/necolas/pen/PZzwBR).
* [TicTacToe](http://codepen.io/necolas/full/eJaLZd/)
* [2048](http://codepen.io/necolas/full/wMVvxj/)
Sample:
```js
import React, { View } from 'react-native-web'
import React, { AppRegistry, Image, StyleSheet, Text, View } from 'react-native'
class MyComponent extends React.Component {
render() {
return (
<View style={styles.root} />
)
}
}
// Components
const Card = ({ children }) => <View style={styles.card}>{children}</View>
const Title = ({ children }) => <Text style={styles.title}>{children}</Text>
const Photo = ({ uri }) => <Image source={{ uri }} style={styles.image} />
const App = () => (
<Card>
<Title>App Card</Title>
<Photo uri="/some-photo.jpg" />
</Card>
)
const styles = {
root: {
borderColor: 'currentcolor'
borderWidth: '5px',
flexDirection: 'row'
height: '5em'
}
}
```
## Components
### [`Image`](docs/components/Image.md)
An accessibile image component with support for image resizing, default image,
and child content.
### [`ListView`](docs/components/ListView.md)
(TODO)
### [`ScrollView`](docs/components/ListView.md)
(TODO)
### [`Swipeable`](docs/components/Swipeable.md)
Touch bindings for swipe gestures.
### [`Text`](docs/components/Text.md)
Displays text as an inline block and supports basic press handling.
### [`TextInput`](docs/components/TextInput.md)
Accessible single- and multi-line text input via a keyboard. Supports features
### [`Touchable`](docs/components/Touchable.md)
Touch bindings for press and long press.
### [`View`](docs/components/View.md)
The fundamental UI building block: layout with flexbox, layout and positioning
styles, and accessibility controls.
## Styling
React Native for Web provides a mechanism to declare all your styles and layout
inline with the components that use them. The `View` component makes it easy to
build common layouts with flexbox, such as stacked and nested boxes with margin
and padding.
Styling is identical to using inline styles in React, but most inline styles
are converted to single-purpose classes. The current implementation includes
300+ precomputed CSS declarations (~4.5KB gzipped) that cover a large
proportion of common styles. A more sophisticated build-time implementation may
produce a slightly larger CSS file for large apps, and fall back to fewer
inline styles. Read more about the [styling
strategy](docs/style.md).
See this [guide to flexbox][flexbox-guide-url].
```js
import React, { Image, Text, View } from 'react-native-web'
class App extends React.Component {
render() {
return (
<View style={styles.row}>
<Image
source={{ uri: 'http://facebook.github.io/react/img/logo_og.png' }}
style={styles.image}
/>
<View style={styles.text}>
<Text style={styles.title}>
React Native Web
</Text>
<Text style={styles.subtitle}>
Build high quality web apps using React
</Text>
</View>
</View>
)
},
})
const styles = {
row: {
flexDirection: 'row',
margin: 40
},
image: {
height: 40,
marginRight: 10,
width: 40,
},
text: {
flex: 1,
// Styles
const styles = StyleSheet.create({
card: {
flexGrow: 1,
justifyContent: 'center'
},
title: {
fontSize: '1.25rem',
fontWeight: 'bold'
},
subtitle: {
fontSize: '1rem'
image: {
height: 40,
marginVertical: 10,
width: 40
}
}
})
// App registration and rendering
AppRegistry.registerComponent('MyApp', () => App)
AppRegistry.runApplication('MyApp', { rootTag: document.getElementById('react-root') })
```
Combine and override style objects:
## Documentation
```js
import baseStyle from './baseStyle'
Guides:
const buttonStyle = {
...baseStyle,
backgroundColor: '#333',
color: '#fff'
}
```
* [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)
## Contributing
Exported modules:
Please read the [contribution guidelines][contributing-url]. Contributions are
welcome!
## Thanks
Thanks to current and past members of the React and React Native teams (in
particular Vjeux and Pete Hunt), and Tobias Koppers for Webpack and CSS loader.
Thanks to [react-swipeable](https://github.com/dogfessional/react-swipeable/)
for the current implementation of `Swipeable`, and to
[react-tappable](https://github.com/JedWatson/react-tappable) for backing the
current implementation of `Touchable`.
* Components
* [`ActivityIndicator`](docs/components/ActivityIndicator.md)
* [`Image`](docs/components/Image.md)
* [`ListView`](docs/components/ListView.md)
* [`Portal`](docs/components/Portal.md)
* [`ScrollView`](docs/components/ScrollView.md)
* [`Text`](docs/components/Text.md)
* [`TextInput`](docs/components/TextInput.md)
* [`TouchableHighlight`](http://facebook.github.io/react-native/releases/0.22/docs/touchablehighlight.html) (mirrors React Native)
* [`TouchableOpacity`](http://facebook.github.io/react-native/releases/0.22/docs/touchableopacity.html) (mirrors React Native)
* [`TouchableWithoutFeedback`](docs/components/TouchableWithoutFeedback.md)
* [`View`](docs/components/View.md)
* APIs
* [`Animated`](http://facebook.github.io/react-native/releases/0.20/docs/animated.html) (mirrors React Native)
* [`AppRegistry`](docs/apis/AppRegistry.md)
* [`AppState`](docs/apis/AppState.md)
* [`AsyncStorage`](docs/apis/AsyncStorage.md)
* [`Dimensions`](docs/apis/Dimensions.md)
* [`NativeMethods`](docs/apis/NativeMethods.md)
* [`NetInfo`](docs/apis/NetInfo.md)
* [`PanResponder`](http://facebook.github.io/react-native/releases/0.20/docs/panresponder.html#content) (mirrors React Native)
* [`PixelRatio`](docs/apis/PixelRatio.md)
* [`Platform`](docs/apis/Platform.md)
* [`StyleSheet`](docs/apis/StyleSheet.md)
## License
Copyright (c) 2015 Nicolas Gallagher. Released under the [MIT
license](http://www.opensource.org/licenses/mit-license.php).
React Native for Web is [BSD licensed](LICENSE).
[contributing-url]: https://github.com/necolas/react-native-web/blob/master/CONTRIBUTING.md
[flexbox-guide-url]: https://css-tricks.com/snippets/css/a-guide-to-flexbox/
[npm-image]: https://img.shields.io/npm/v/react-native-web.svg
[npm-image]: https://badge.fury.io/js/react-native-web.svg
[npm-url]: https://npmjs.org/package/react-native-web
[react-native-url]: https://facebook.github.io/react-native/
[travis-image]: https://travis-ci.org/necolas/react-native-web.svg?branch=master

11
config/constants.js Normal file
View File

@@ -0,0 +1,11 @@
var path = require('path')
var ROOT = path.join(__dirname, '..')
module.exports = {
DIST_DIRECTORY: path.join(ROOT, 'dist'),
EXAMPLES_DIRECTORY: path.join(ROOT, 'examples'),
SRC_DIRECTORY: path.join(ROOT, 'src'),
ROOT_DIRECTORY: ROOT,
TEST_ENTRY: path.join(ROOT, 'tests.webpack.js')
}

View File

@@ -1,46 +1,55 @@
var assign = require('object-assign')
var path = require('path')
var webpackConfig = require('./webpack-base.config.js')
var constants = require('./constants')
var webpack = require('webpack')
module.exports = function (config) {
config.set({
basePath: path.resolve(__dirname, '..'),
browsers: [ process.env.TRAVIS ? 'Firefox' : 'Chrome' ],
basePath: constants.ROOT_DIRECTORY,
browsers: process.env.TRAVIS ? [ 'Firefox' ] : [ 'Chrome' ],
browserNoActivityTimeout: 60000,
client: {
captureConsole: true,
mocha: {
ui: 'tdd'
},
mocha: { ui: 'tdd' },
useIframe: true
},
files: [
'src/specs.bundle.js'
],
frameworks: [
'mocha'
constants.TEST_ENTRY
],
frameworks: [ 'mocha' ],
plugins: [
'karma-chrome-launcher',
'karma-firefox-launcher',
'karma-mocha',
'karma-sourcemap-loader',
'karma-spec-reporter',
'karma-webpack'
],
preprocessors: {
'src/specs.bundle.js': [ 'webpack', 'sourcemap' ]
[constants.TEST_ENTRY]: [ 'webpack', 'sourcemap' ]
},
reporters: [ 'dots' ],
reporters: process.env.TRAVIS ? [ 'dots' ] : [ 'spec' ],
singleRun: true,
webpack: assign({}, webpackConfig, { devtool: 'inline' }),
webpackMiddleware: {
stats: {
assetsSort: 'name',
colors: true,
children: false,
chunks: false,
modules: false
}
webpack: {
devtool: 'inline-source-map',
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,23 +0,0 @@
var autoprefixer = require('autoprefixer-core')
module.exports = {
module: {
loaders: [
{
test: /\.css$/,
loader: [
'style-loader',
'css-loader?module&localIdentName=[hash:base64:5]',
'!postcss-loader'
].join('!')
},
{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: 'babel-loader',
query: { cacheDirectory: true }
}
]
},
postcss: [ autoprefixer ]
}

View File

@@ -0,0 +1,37 @@
const constants = require('./constants')
const path = require('path')
const webpack = require('webpack')
module.exports = {
devServer: {
contentBase: constants.EXAMPLES_DIRECTORY
},
entry: {
example: constants.EXAMPLES_DIRECTORY
},
module: {
loaders: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
query: { cacheDirectory: true }
}
]
},
output: {
filename: 'examples.js'
},
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
}),
new webpack.optimize.DedupePlugin(),
new webpack.optimize.OccurenceOrderPlugin()
],
resolve: {
alias: {
'react-native': path.join(__dirname, '../src')
}
}
}

View File

@@ -1,12 +1,26 @@
var assign = require('object-assign')
var base = require('./webpack-base.config.js')
var constants = require('./constants')
var webpack = require('webpack')
var UglifyJsPlugin = webpack.optimize.UglifyJsPlugin
var plugins = []
if (process.env.NODE_ENV === 'production') {
plugins.push(
new UglifyJsPlugin({
module.exports = {
entry: {
main: constants.DIST_DIRECTORY
},
externals: [{
'react': true,
'react-dom': true,
'react-dom/server': true
}],
output: {
filename: 'react-native-web.js',
library: 'ReactNativeWeb',
libraryTarget: 'umd',
path: constants.DIST_DIRECTORY
},
plugins: [
new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('production') }),
new webpack.optimize.DedupePlugin(),
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.optimize.UglifyJsPlugin({
compress: {
dead_code: true,
drop_console: true,
@@ -14,21 +28,5 @@ if (process.env.NODE_ENV === 'production') {
warnings: true
}
})
)
]
}
module.exports = assign({}, base, {
entry: {
main: './src/index'
},
externals: [{
react: true
}],
output: {
filename: 'react-native-web.js',
library: 'ReactNativeWeb',
libraryTarget: 'commonjs2',
path: './dist'
},
plugins: plugins
})

60
docs/apis/AppRegistry.md Normal file
View File

@@ -0,0 +1,60 @@
# AppRegistry
`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
guide](../guides/rendering.md) for more details).
To "stop" an application when a view should be destroyed, call
`AppRegistry.unmountApplicationComponentAtRootTag` with the tag that was passed
into `runApplication`. These should always be used as a pair.
## Methods
(web) static **prerenderApplication**(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; }`, where
`html` the prerendered HTML, and `style` is the prerendered style sheet.
static **registerConfig**(config: Array<AppConfig>)
Registry multiple applications. `AppConfig` is of type `{ appKey: string;
component: ComponentProvider; run?: Function }`.
static **registerComponent**(appKey: string, getComponentFunc: ComponentProvider)
Register a component provider under the given `appKey`.
static **registerRunnable**(appKey: string, run: Function)
Register a custom render function for an application. The function will receive
the `appParameters` passed to `runApplication`.
static **getAppKeys**()
Returns all registered app keys.
static **runApplication**(appKey: string, appParameters?: object)
Runs the application that was registered under `appKey`. The `appParameters`
must include the `rootTag` into which the application is rendered, and
optionally any `initialProps`.
static **unmountApplicationComponentAtRootTag**(rootTag: HTMLElement)
To "stop" an application when a view should be destroyed, call
`AppRegistry.unmountApplicationComponentAtRootTag` with the tag that was passed
into `runApplication`
## Example
```js
AppRegistry.registerComponent('MyApp', () => AppComponent)
AppRegistry.runApplication('MyApp', {
initialProps: {},
rootTag: document.getElementById('react-root')
})
```

61
docs/apis/AppState.md Normal file
View File

@@ -0,0 +1,61 @@
## AppState
`AppState` can tell you if the app is in the foreground or background, and
notify you when the state changes.
States
* `active` - The app is running in the foreground
* `background` - The app is running in the background (i.e., the user has not focused the app's tab).
## Properties
static **currentState**
Returns the current state of the app: `active` or `background`.
## Methods
static **addEventListener**(type: string, handler: Function)
Add a handler to `AppState` changes by listening to the `change` event type and
providing the `handler`. The handler is called with the app state value.
static **removeEventListener**(type: string, handler: Function)
Remove a handler by passing the change event `type` and the `handler`.
## Examples
To see the current state, you can check `AppState.currentState`, which will be
kept up-to-date. This example will only ever appear to say "Current state is:
active" because the app is only visible to the user when in the `active` state,
and the null state will happen only momentarily.
```js
class Example extends React.Component {
constructor(props) {
super(props)
this.state = { currentAppState: AppState.currentState }
this._handleAppStateChange = this._handleAppStateChange.bind(this)
}
componentDidMount() {
AppState.addEventListener('change', this._handleAppStateChange);
}
componentWillUnmount() {
AppState.removeEventListener('change', this._handleAppStateChange);
}
_handleAppStateChange(currentAppState) {
this.setState({ currentAppState });
}
render() {
return (
<Text>Current state is: {this.state.currentAppState}</Text>
)
}
}
```

71
docs/apis/AsyncStorage.md Normal file
View File

@@ -0,0 +1,71 @@
# AsyncStorage
`AsyncStorage` is a simple, asynchronous, persistent, key-value storage system
that is global to the domain. It's a facade over, and should be used instead of
`window.localStorage` to provide an asynchronous API and multi functions. Each
method returns a `Promise` object.
It is recommended that you use an abstraction on top of `AsyncStorage` instead
of `AsyncStorage` directly for anything more than light usage since it operates
globally.
The batched functions are useful for executing a lot of operations at once,
allowing for optimizations to provide the convenience of a single promise after
all operations are complete.
## Methods
static **clear**()
Erases all AsyncStorage. You probably don't want to call this - use
`removeItem` or `multiRemove` to clear only your own keys instead. Returns a
Promise object.
static **getAllKeys**()
Gets all known keys. Returns a Promise object.
static **getItem**(key: string)
Fetches the value of the given key. Returns a Promise object.
static **mergeItem**(key: string, value: string)
Merges existing value with input value, assuming they are stringified JSON.
Returns a Promise object.
static **multiGet**(keys: Array<string>)
`multiGet` results in an array of key-value pair arrays that matches the input
format of `multiSet`. Returns a Promise object.
```js
multiGet(['k1', 'k2']) -> [['k1', 'val1'], ['k2', 'val2']]
```
static **multiMerge**(keyValuePairs: Array<Array<string>>)
multiMerge takes an array of key-value array pairs that match the output of
`multiGet`. It merges existing values with input values, assuming they are
stringified JSON. Returns a Promise object.
static **multiRemove**(keys: Array<string>)
Delete all the keys in the keys array. Returns a Promise object.
static **multiSet**(keyValuePairs: Array<Array<string>>)
`multiSet` takes an array of key-value array pairs that match the output of
`multiGet`. Returns a Promise object.
```js
multiSet([['k1', 'val1'], ['k2', 'val2']]);
```
static **removeItem**(key: string)
Removes the value of the given key. Returns a Promise object.
static **setItem**(key: string, value: string)
Sets the value of the given key. Returns a Promise object.

13
docs/apis/Dimensions.md Normal file
View File

@@ -0,0 +1,13 @@
# Dimensions
Note: dimensions may change (e.g due to device rotation) so any rendering logic
or styles that depend on these constants should try to call this function on
every render, rather than caching the value.
## Methods
static **get**(dimension: string)
Get a dimension (e.g., `"window"` or `"screen"`).
Example: `const { height, width } = Dimensions.get('window')`

View File

@@ -0,0 +1,42 @@
# NativeMethods
React Native for Web provides several methods to directly access the underlying
DOM node. This can be useful in cases when you want to focus a view or measure
its on-screen dimensions, for example.
The methods described are available on most of the default components provided
by React Native for Web. Note, however, that they are *not* available on the
composite components that you define in your own app. For more information, see
[Direct Manipulation](../guides/direct-manipulation.md).
## Methods
**blur**()
Removes focus from an input or view. This is the opposite of `focus()`.
**focus**()
Requests focus for the given input or view. The exact behavior triggered will
depend the type of view.
**measure**(callback: (x, y, width, height, pageX, pageY) => void)
For a given view, `measure` determines the offset relative to the parent view,
width, height, and the offset relative to the viewport. Returns the values via
an async callback.
Note that these measurements are not available until after the rendering has
been completed.
**measureLayout**(relativeToNativeNode: DOMNode, onSuccess: (x, y, width, height) => void)
Like `measure`, but measures the view relative to another view, specified as
`relativeToNativeNode`. This means that the returned `x`, `y` are relative to
the origin `x`, `y` of the ancestor view.
**setNativeProps**(nativeProps: Object)
This function sends props straight to the underlying DOM node. See the [direct
manipulation](../guides/direct-manipulation.md) guide for cases where
`setNativeProps` should be used.

77
docs/apis/NetInfo.md Normal file
View File

@@ -0,0 +1,77 @@
# NetInfo
`NetInfo` asynchronously determines the online/offline status of the
application.
Connection types:
* `bluetooth` - The user agent is using a Bluetooth connection.
* `cellular` - The user agent is using a cellular connection (e.g., EDGE, HSPA, LTE, etc.).
* `ethernet` - The user agent is using an Ethernet connection.
* `mixed` - The user agent is using multiple connection types.
* `none` - The user agent will not contact the network (offline).
* `other` - The user agent is using a connection type that is not one of enumerated connection types.
* `unknown` - The user agent has established a network connection, but is unable to determine what is the underlying connection technology.
* `wifi` - The user agent is using a Wi-Fi connection.
* `wimax` - The user agent is using a WiMAX connection.
## Methods
Note that support for retrieving the connection type depends upon browswer
support (and is limited to mobile browsers). It will default to `unknown` when
support is missing.
static **addEventListener**(eventName: ChangeEventName, handler: Function)
static **fetch**(): Promise
static **removeEventListener**(eventName: ChangeEventName, handler: Function)
## Properties
**isConnected**
Available on all user agents. Asynchronously fetch a boolean to determine
internet connectivity.
**isConnected.addEventListener**(eventName: ChangeEventName, handler: Function)
**isConnected.fetch**(): Promise
**isConnected.removeEventListener**(eventName: ChangeEventName, handler: Function)
## Examples
Fetching the connection type:
```js
NetInfo.fetch().then((connectionType) => {
console.log('Connection type:', connectionType);
});
```
Subscribing to changes in the connection type:
```js
const handleConnectivityTypeChange = (connectionType) => {
console.log('Current connection type:', connectionType);
}
NetInfo.addEventListener('change', handleConnectivityTypeChange);
```
Fetching the connection status:
```js
NetInfo.isConnected.fetch().then((isConnected) => {
console.log('Connection status:', (isConnected ? 'online' : 'offline'));
});
```
Subscribing to changes in the connection status:
```js
const handleConnectivityStatusChange = (isConnected) => {
console.log('Current connection status:', (isConnected ? 'online' : 'offline'));
}
NetInfo.isConnected.addEventListener('change', handleConnectivityStatusChange);
```

51
docs/apis/PixelRatio.md Normal file
View File

@@ -0,0 +1,51 @@
# PixelRatio
`PixelRatio` gives access to the device pixel density.
## Methods
static **get**()
Returns the device pixel density. Some examples:
* PixelRatio.get() === 1
* mdpi Android devices (160 dpi)
* PixelRatio.get() === 1.5
* hdpi Android devices (240 dpi)
* PixelRatio.get() === 2
* iPhone 4, 4S
* iPhone 5, 5c, 5s
* iPhone 6
* xhdpi Android devices (320 dpi)
* PixelRatio.get() === 3
* iPhone 6 plus
* xxhdpi Android devices (480 dpi)
* PixelRatio.get() === 3.5
* Nexus 6
static **getPixelSizeForLayoutSize**(layoutSize: number)
Converts a layout size (dp) to pixel size (px). Guaranteed to return an integer
number.
static **roundToNearestPixel**(layoutSize: number)
Rounds a layout size (dp) to the nearest layout size that corresponds to an
integer number of pixels. For example, on a device with a PixelRatio of 3,
`PixelRatio.roundToNearestPixel(8.4)` = `8.33`, which corresponds to exactly
`(8.33 * 3)` = `25` pixels.
## Examples
Fetching a correctly sized image. You should get a higher resolution image if
you are on a high pixel density device. A good rule of thumb is to multiply the
size of the image you display by the pixel ratio.
```js
const image = getImage({
width: PixelRatio.getPixelSizeForLayoutSize(200),
height: PixelRatio.getPixelSizeForLayoutSize(100),
});
<Image source={image} style={{ width: 200, height: 100 }} />
```

28
docs/apis/Platform.md Normal file
View File

@@ -0,0 +1,28 @@
# Platform
Detect what is the platform in which the app is running. This piece of
functionality can be useful when only small parts of a component are platform
specific.
## Properties
**OS**: string
`Platform.OS` will be `web` when running in a Web browser.
**userAgent**: string
On Web, the `Platform` module can be also be used to detect the browser
`userAgent`.
## Examples
```js
const styles = StyleSheet.create({
height: (Platform.OS === 'web') ? 200 : 100,
});
if (Platform.userAgent.includes('Android')) {
console.log('Running on Android!');
}
```

57
docs/apis/StyleSheet.md Normal file
View File

@@ -0,0 +1,57 @@
# StyleSheet
The `StyleSheet` abstraction converts predefined styles to (vendor-prefixed)
CSS without requiring a compile-time step. Some styles cannot be resolved
outside of the render loop and are applied as inline styles. Read more about to
[how style your application](docs/guides/style).
## Methods
**create**(obj: {[key: string]: any})
Each key of the object passed to `create` must define a style object.
## Example
```js
const styles = StyleSheet.create({
container: {
borderRadius: 4,
borderWidth: 0.5,
borderColor: '#d6d7da',
},
title: {
fontSize: 19,
fontWeight: 'bold',
},
activeTitle: {
color: 'red',
}
})
```
Use styles:
```js
<View style={styles.container}>
<Text
style={[
styles.title,
this.props.isActive && styles.activeTitle
]}
/>
</View>
```
Or:
```js
<View style={styles.container}>
<Text
style={{
...styles.title,
...(this.props.isActive && styles.activeTitle)
}}
/>
</View>
```

View File

@@ -0,0 +1,69 @@
# ActivityIndicator
## Props
[...View props](./View.md)
**animating**: bool = true
Whether to show the indicator (true, the default) or hide it (false).
**color**: string = #999999
The foreground color of the spinner (default is gray).
**hidesWhenStopped**: bool = true
Whether the indicator should hide when not animating (true by default).
**size**: oneOf('small, 'large')
Size of the indicator. Small has a height of `20`, large has a height of `36`.
## Examples
```js
import React, { ActivityIndicator, Component, StyleSheet, View } from 'react-native'
class ToggleAnimatingActivityIndicator extends Component {
constructor(props) {
super(props)
this.state = { animating: true }
}
componentDidMount: function() {
this.setToggleTimeout();
}
render() {
return (
<ActivityIndicator
animating={this.state.animating}
size="large"
style={[styles.centering, { height: 80 }]}
/>
);
}
_setToggleTimeout() {
setTimeout(() => {
this.setState({ animating: !this.state.animating })
this._setToggleTimeout()
}, 1200)
}
})
const styles = StyleSheet.create({
centering: {
alignItems: 'center',
justifyContent: 'center'
},
gray: {
backgroundColor: '#cccccc'
},
horizontal: {
flexDirection: 'row',
justifyContent: 'space-around'
}
})
```

View File

@@ -3,75 +3,87 @@
An accessibile image component with support for image resizing, default image,
and child content.
Unsupported React Native props:
`capInsets` (ios),
`onProgress` (ios)
## Props
**accessibilityLabel** string
**accessibilityLabel**: string
The text that's read by the screen reader when the user interacts with the image.
The text that's read by a screenreader when someone interacts with the image.
**children** any
**accessible**: bool
When `false`, the view is hidden from screenreaders. Default: `true`.
**children**: any
Content to display over the image.
**defaultSource** { uri: string }
**defaultSource**: { uri: string }
An image to display as a placeholder while downloading the final image off the network.
**onError** function
**onError**: function
Invoked on load error with `{nativeEvent: {error}}`.
**onLoad** function
**onLayout**: function
TODO
**onLoad**: function
Invoked when load completes successfully.
**onLoadEnd** function
**onLoadEnd**: function
Invoked when load either succeeds or fails,
**onLoadStart** function
**onLoadStart**: function
Invoked on load start.
**resizeMode** oneOf('clip', 'contain', 'cover', 'stretch')
**resizeMode**: oneOf('contain', 'cover', 'none', 'stretch') = 'stretch'
Determines how to resize the image when the frame doesn't match the raw image
dimensions. Default: `cover`.
dimensions.
**source** { uri: string }
**source**: { uri: string }
`uri` is a string representing the resource identifier for the image, which
could be an http address or a base64 encoded image.
**style** style
**style**: style
[View](View.md) style
+ ...[View#style](View.md)
+ `resizeMode`
Defaults:
```js
{
alignSelf: 'flex-start',
backgroundColor: 'lightGray'
}
```
**testID** string
**testID**: string
Used to locate a view in end-to-end tests.
## Properties
static **resizeMode**: Object
Example usage:
```
<Image resizeMode={Image.resizeMode.contain} />
```
## Examples
```js
import placeholderAvatar from './placeholderAvatar.png'
import React, { Image } from 'react-native-web'
import React, { Component, Image, PropTypes, StyleSheet } from 'react-native'
const { Component, PropTypes } = React;
class Avatar extends Component {
export default class ImageExample extends Component {
constructor(props, context) {
super(props, context)
this.state = { isLoaded: false }
this.state = { loading: true }
}
static propTypes = {
@@ -86,12 +98,12 @@ class Avatar extends Component {
_onLoad(e) {
console.log('Avatar.onLoad', e)
this.setState({ isLoaded: true })
this.setState({ loading: false })
}
render() {
const { size, testID, user } = this.props
const { isLoaded } = this.state
const loadingStyle = this.state.loading ? { styles.loading } : { }
return (
<Image
@@ -100,33 +112,36 @@ class Avatar extends Component {
onLoad={this._onLoad.bind(this)}
resizeMode='cover'
source={{ uri: user.avatarUrl }}
style={ ...style.base, ...style[size], ...style[isLoaded] }
style={[
styles.base,
styles[size],
loadingStyle
]}
/>
)
}
}
const style = {
const styles = StyleSheet.create({
base: {
borderColor: 'white',
borderRadius: '5px',
borderWidth: '5px',
opacity: 0.5,
borderRadius: 5,
borderWidth: 5
},
loading: {
opacity: 0.5
},
small: {
height: '32px',
width: '32px'
height: 32,
width: 32
},
normal: {
height: '48px',
width: '48px'
height: 48,
width: 48
},
large: {
height: '64px',
width: '64px'
height: 64,
width: 64
}
isLoaded: {
opacity: 1
}
}
})
```

View File

@@ -1,36 +1,26 @@
# ListView
TODO
## Props
**children** any
**children**: any
Content to display over the image.
**style** style
+ `property` type
Defaults:
```js
{
}
```
**style**: style
+ ...[View#style](View.md)
## Examples
```js
import React, { ListView } from 'react-native-web'
import React, { Component, ListView, PropTypes } from 'react-native'
const { Component, PropTypes } = React;
export default class ListViewExample extends Component {
static propTypes = {}
class Example extends Component {
static propTypes = {
}
static defaultProps = {
}
static defaultProps = {}
render() {
return (

67
docs/components/Portal.md Normal file
View File

@@ -0,0 +1,67 @@
# Portal
`Portal` is used to render modal content on top of everything else in the
application. It passes modal views all the way up to the root element created
by `AppRegistry.runApplication`.
There can only be one `Portal` instance rendered in an application, and this
instance is controlled by React Native for Web.
## Methods
static **allocateTag**()
Creates a new unique tag for the modal that your component is rendering. A
good place to allocate a tag is in `componentWillMount`. Returns a string. See
`showModal` and `closeModal`.
static **closeModal**(tag: string)
Remove a modal from the collection of modals to be rendered. The `tag` must
exactly match the tag previous passed to `showModal` to identify the React
component.
static **getOpenModals**()
Get an array of all the open modals, as identified by their tag string.
static **showModal**(tag: string, component: any)
Render a new modal. The `tag` must be unique as it is used to identify the
React component to render. This same tag can later be used in `closeModal`.
## Examples
```js
import React, { Portal, Text, Touchable } from 'react-native'
export default class PortalExample extends Component {
componentWillMount() {
this._portalTag = Portal.allocateTag()
}
render() {
return (
<Touchable onPress={this._handlePortalOpen.bind(this)}>
<Text>Open portal</Text>
</Touchable>
)
}
_handlePortalClose(e) {
Portal.closeModal(this._portalTag)
}
_handlePortalOpen(e) {
Portal.showModal(this._portalTag, this._renderPortalContent())
}
_renderPortalContent() {
return (
<Touchable onPress={this._handlePortalClose.bind(this)}>
<Text>Close portal</Text>
</Touchable>
)
}
}
```

View File

@@ -1,40 +1,123 @@
# ScrollView
A scrollable `View` that provides itegration with the touch-locking "responder"
system. `ScrollView`'s must have a bounded height: either set the height of the
view directly (discouraged) or make sure all parent views have bounded height
(e.g., transfer `{ flex: 1}` down the view stack).
## Props
**children** any
[...View props](./View.md)
Content to display over the image.
**contentContainerStyle**: style
**style** style
These styles will be applied to the scroll view content container which wraps
all of the child views.
+ `property` type
**horizontal**: bool = false
Defaults:
When true, the scroll view's children are arranged horizontally in a row
instead of vertically in a column.
```js
{
}
```
**keyboardDismissMode**: oneOf('none', 'on-drag') = 'none'
Determines whether the keyboard gets dismissed in response to a scroll drag.
* `none` (the default), drags do not dismiss the keyboard.
* `on-drag`, the keyboard is dismissed when a drag begins.
* `interactive` (not supported on web; same as `none`)
**onContentSizeChange**: function
TODO
Called when scrollable content view of the `ScrollView` changes. It's
implemented using the `onLayout` handler attached to the content container
which this `ScrollView` renders.
**onScroll**: function
Fires at most once per frame during scrolling. The frequency of the events can
be contolled using the `scrollEventThrottle` prop.
**refreshControl**: element
TODO
A [RefreshControl](../RefreshControl) component, used to provide
pull-to-refresh functionality for the `ScrollView`.
**scrollEnabled**: bool = true
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
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.
## Instance methods
**getInnerViewNode()**: any
Returns a reference to the underlying content container DOM node within the `ScrollView`.
**getScrollableNode()**: any
Returns a reference to the underlying scrollable DOM node.
**getScrollResponder()**: Component
Returns a reference to the underlying scroll responder, which supports
operations like `scrollTo`. All `ScrollView`-like components should implement
this method so that they can be composed while providing access to the
underlying scroll responder's methods.
**scrollTo(options: { x: number = 0; y: number = 0; animated: boolean = true })**
Scrolls to a given `x`, `y` offset (animation is not currently supported).
## Examples
```js
import React, { ScrollView } from 'react-native-web'
import React, { Component, ScrollView, StyleSheet } from 'react-native'
import Item from './Item'
const { Component, PropTypes } = React;
class Example extends Component {
static propTypes = {
export default class ScrollViewExample extends Component {
constructor(props, context) {
super(props, context)
this.state = {
items: Array.from(new Array(20)).map((_, i) => ({ id: i }))
}
}
static defaultProps = {
onScroll(e) {
console.log(e)
}
render() {
return (
<ScrollView
children={this.state.items.map((item) => <Item {...item} />)}
contentContainerStyle={styles.container}
horizontal
onScroll={(e) => this.onScroll(e)}
scrollEventThrottle={60}
style={styles.root}
/>
)
}
}
const styles = StyleSheet.create({
root: {
borderWidth: 1
},
container: {
padding: 10
}
})
```

View File

@@ -1,93 +0,0 @@
# Swipeable
## Props
**delta** number
Number of pixels that must be swiped before events are dispatched. Default: `10`.
**flickThreshold** number
The velocity threshold at which a swipe is considered a flick. Default: `0.6`.
**onSwiped** function
(SyntheticTouchEvent, deltaX, deltaY, isFlick) => swipeHandler
Called once a swipe has ended.
**onSwipedDown** function
(SyntheticTouchEvent, delta, isFlick) => swipeHandler
Called once a swipe-down has ended.
**onSwipedLeft** function
(SyntheticTouchEvent, delta, isFlick) => swipeHandler
Called once a swipe-left has ended.
**onSwipedUp** function
(SyntheticTouchEvent, delta, isFlick) => swipeHandler
Called once a swipe-up has ended.
**onSwipedRight** function
(SyntheticTouchEvent, delta, isFlick) => swipeHandler
Called once a swipe-right has ended.
**onSwipingDown** function
(SyntheticTouchEvent, delta, isFlick) => swipeHandler
Called while a swipe-down is in progress.
**onSwipingLeft** function
(SyntheticTouchEvent, delta, isFlick) => swipeHandler
Called while a swipe-left is in progress.
**onSwipingRight** function
(SyntheticTouchEvent, delta, isFlick) => swipeHandler
Called while a swipe-right is in progress.
**onSwipingUp** function
(SyntheticTouchEvent, delta, isFlick) => swipeHandler
Called while a swipe-up is in progress.
## Examples
```js
import React, { Swipeable } from 'react-native-web'
const { Component, PropTypes } = React;
class Example extends Component {
static propTypes = {
}
static defaultProps = {
}
_onSwiped(event, x, y, isFlick) {
}
render() {
return (
<Swipeable
onSwiped={this._onSwiped.bind(this)}
/>
)
}
}
```

View File

@@ -1,75 +1,95 @@
# Text
`Text` is component for displaying text. It supports style, basic touch
handling, and inherits typographic styles from ancestor elements. In a
divergence from React Native, components other than `Text` can be children of a
`Text` component.
handling, and inherits typographic styles from ancestor elements.
The `Text` is unique relative to layout: child elements use text layout
(`inline-block`) rather than flexbox layout. This means that elements inside of
a `Text` are not rectangles, as they wrap when reaching the edge of their
(`inline`) rather than flexbox layout. This means that elements inside of a
`Text` are not rectangles, as they wrap when reaching the edge of their
container.
Unsupported React Native props:
`allowFontScaling` (ios),
`suppressHighlighting` (ios)
## Props
**children** any
NOTE: `Text` will transfer all other props to the rendered HTML element.
Child content
(web) **accessibilityLabel**: string
**component** function, string
Defines the text available to assistive technologies upon interaction with the
element. (This is implemented using `aria-label`.)
Default is `span`.
(web) **accessibilityRole**: oneOf(roles)
**numberOfLines** number
Allows assistive technologies to present and support interaction with the view
in a manner that is consistent with user expectations for similar views of that
type. For example, marking a touchable view with an `accessibilityRole` of
`button`. (This is implemented using [ARIA roles](http://www.w3.org/TR/wai-aria/roles#role_definitions)).
Truncates the text with an ellipsis after this many lines.
Note: Avoid changing `accessibilityRole` values over time or after user
actions. Generally, accessibility APIs do not provide a means of notifying
assistive technologies of a `role` value change.
**onPress** function
(web) **accessible**: bool = true
When `false`, the text is hidden from assistive technologies. (This is
implemented using `aria-hidden`.)
**children**: any
Child content.
**numberOfLines**: number
Truncates the text with an ellipsis after this many lines. Currently only supports `1`.
**onPress**: function
This function is called on press.
**style** style
**style**: style
+ `backgroundColor`
+ ...[View#style](View.md)
+ `color`
+ `direction`
+ `fontFamily`
+ `fontSize`
+ `fontStyle`
+ `fontWeight`
+ `letterSpacing`
+ `lineHeight`
+ `margin`
+ `padding`
+ `textAlign`
+ `textDecoration`
+ `textShadow`
+ `textTransform`
+ `whiteSpace`
+ `wordWrap`
+ `writingDirection`
**testID** string
**testID**: string
Used to locate this view in end-to-end tests.
## Examples
```js
import React, { Text } from 'react-native-web'
import React, { Component, PropTypes, StyleSheet, Text } from 'react-native'
const { Component, PropTypes } = React
class PrettyText extends Component {
export default class PrettyText extends Component {
static propTypes = {
...Text.propTypes,
color: PropTypes.oneOf(['white', 'gray', 'red']),
size: PropTypes.oneOf(['small', 'normal', 'large']),
weight: PropTypes.oneOf(['light', 'normal', 'bold'])
}
};
static defaultProps = {
...Text.defaultProps,
color: 'gray',
size: 'normal',
weight: 'normal'
}
};
render() {
const { color, size, style, weight, ...other } = this.props;
@@ -77,32 +97,32 @@ class PrettyText extends Component {
return (
<Text
...other
style={{
...style,
...localStyle.color[color],
...localStyle.size[size],
...localStyle.weight[weight]
}}
style={[
style,
colorStyles[color],
sizeStyles[size],
weightStyles[weight]
]}
/>
);
}
}
const localStyle = {
color: {
white: { color: 'white' },
gray: { color: 'gray' },
red: { color: 'red' }
},
size: {
small: { fontSize: '0.85rem', padding: '0.5rem' },
normal: { fontSize: '1rem', padding: '0.75rem' },
large: { fontSize: '1.5rem', padding: '1rem' }
},
weight: {
light: { fontWeight: '300' },
normal: { fontWeight: '400' },
bold: { fontWeight: '700' }
}
}
const colorStyles = StyleSheet.create({
white: { color: 'white' },
gray: { color: 'gray' },
red: { color: 'red' }
})
const sizeStyles = StyleSheet.create({
small: { fontSize: '0.85rem', padding: '0.5rem' },
normal: { fontSize: '1rem', padding: '0.75rem' },
large: { fontSize: '1.5rem', padding: '1rem' }
})
const weightStyles = StyleSheet.create({
light: { fontWeight: '300' },
normal: { fontWeight: '400' },
bold: { fontWeight: '700' }
})
```

View File

@@ -5,107 +5,207 @@ 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),
`returnKeyType` (ios),
`selectionState` (ios),
`textAlign` (android),
`textAlignVertical` (android),
`underlineColorAndroid` (android)
## Props
**autoComplete** bool
(web) **accessibilityLabel**: string
Defines the text label available to assistive technologies upon interaction
with the element. (This is implemented using `aria-label`.)
(web) **autoComplete**: bool = false
Indicates whether the value of the control can be automatically completed by the browser.
**autoFocus** bool
**autoFocus**: bool = false
If true, focuses the input on `componentDidMount`. Only the first form element
in a document with `autofocus` is focused. Default: `false`.
in a document with `autofocus` is focused.
**defaultValue** string
**clearTextOnFocus**: bool = false
If `true`, clears the text field automatically when focused.
**defaultValue**: string
Provides an initial value that will change when the user starts typing. Useful
for simple use-cases where you don't want to deal with listening to events and
updating the `value` prop to keep the controlled state in sync.
**editable** bool
**editable**: bool = true
If false, text is not editable. Default: `true`.
If `false`, text is not editable (i.e., read-only).
**keyboardType** oneOf('default', 'email', 'numeric', 'search', 'tel', 'url')
**keyboardType**: oneOf('default', 'email-address', 'numeric', 'phone-pad', 'search', 'url', 'web-search') = 'default'
Determines which keyboard to open, e.g. `email`. Default: `default`. (Not
available when `multiline` is `true`.)
Determines which keyboard to open. (NOTE: Safari iOS requires an ancestral
`<form action>` element to display the `search` keyboard).
**multiline** bool
(Not available when `multiline` is `true`.)
If true, the text input can be multiple lines. Default: `false`.
**maxLength**: number
**onBlur** function
Limits the maximum number of characters that can be entered.
(web) **maxNumberOfLines**: number = numberOfLines
Limits the maximum number of lines for a multiline `TextInput`.
(Requires `multiline` to be `true`.)
**multiline**: bool = false
If true, the text input can be multiple lines.
**numberOfLines**: number = 2
Sets the initial number of lines for a multiline `TextInput`.
(Requires `multiline` to be `true`.)
**onBlur**: function
Callback that is called when the text input is blurred.
**onChange** function
**onChange**: function
Callback that is called when the text input's text changes.
**onChangeText** function
**onChangeText**: function
Callback that is called when the text input's text changes. Changed text is
passed as an argument to the callback handler.
Callback that is called when the text input's text changes. The text is passed
as an argument to the callback handler.
**onFocus** function
**onFocus**: function
Callback that is called when the text input is focused.
**placeholder** string
**onLayout**: function
The string that will be rendered before text input has been entered.
TODO
**placeholderTextColor** string
(web) **onSelectionChange**: function
Callback that is called when the text input's selection changes. The following
object is passed as an argument to the callback handler.
```js
{
selectionDirection,
selectionEnd,
selectionStart,
nativeEvent
}
```
**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
**secureTextEntry**: bool = false
If true, the text input obscures the text entered so that sensitive text like
passwords stay secure. Default: `false`. (Not available when `multiline` is `true`.)
passwords stay secure.
**style** style
(Not available when `multiline` is `true`.)
[View](View.md) style
**selectTextOnFocus**: bool = false
+ `color`
+ `direction`
+ `fontFamily`
+ `fontSize`
+ `fontStyle`
+ `fontWeight`
+ `letterSpacing`
+ `lineHeight`
+ `textAlign`
+ `textDecoration`
+ `textTransform`
If `true`, all text will automatically be selected on focus.
**testID** string
**style**: style
+ ...[Text#style](Text.md)
+ `outline`
**testID**: string
Used to locate this view in end-to-end tests.
**value**: string
The value to show for the text input. `TextInput` is a controlled component,
which means the native `value` will be forced to match this prop if provided.
Read about how [React form
components](https://facebook.github.io/react/docs/forms.html) work. To prevent
user edits to the value set `editable={false}`.
## Instance methods
**blur()**
Blur the underlying DOM input.
**clear()**
Clear the text from the underlying DOM input.
**focus()**
Focus the underlying DOM input.
## Examples
```js
import React, { TextInput } from 'react-native-web'
import React, { Component, StyleSheet, TextInput } from 'react-native'
const { Component, PropTypes } = React
class AppTextInput extends Component {
static propTypes = {
export default class TextInputExample extends Component {
constructor(props, context) {
super(props, context)
this.state = { isFocused: false }
}
static defaultProps = {
_onBlur(e) {
this.setState({ isFocused: false })
}
_onFocus(e) {
this.setState({ isFocused: true })
}
render() {
return (
<TextInput />
<TextInput
accessibilityLabel='Write a status update'
maxNumberOfLines={5}
multiline
numberOfLines={2}
onBlur={this._onBlur.bind(this)}
onFocus={this._onFocus.bind(this)}
placeholder={`What's happening?`}
style={[
styles.default
this.state.isFocused && styles.focused
]}
/>
);
}
}
const styles = {
}
const styles = StyleSheet.create({
default: {
borderColor: 'gray',
borderBottomWidth: 2
},
focused: {
borderColor: 'blue'
}
})
```

View File

@@ -1,59 +0,0 @@
# Touchable
## Props
**activeHighlight** string
Sets the color of the background highlight when `onPressIn` is called. The
highlight is removed when `onPressOut` is called. Default: `transparent`.
**activeOpacity** number
Sets the opacity of the child view when `onPressIn` is called. The opacity is
reset when `onPressOut` is called. Default: `1`.
**component** function or string
The backing component. Default: `div`.
**delayLongPress** number
Delay in ms, from `onPressIn`, before `onLongPress` is called. Default: `1000`.
**delayPressIn** number (TODO)
Delay in ms, from the start of the touch, before `onPressIn` is called. Default: `0`.
**delayPressOut** number (TODO)
Delay in ms, from the release of the touch, before `onPressOut` is called. Default: `0`.
**onLongPress** function
**onPress** function
**onPressIn** function
**onPressOut** function
## Examples
```js
import React, { Touchable } from 'react-native-web'
const { Component, PropTypes } = React;
class Example extends Component {
static propTypes = {
}
static defaultProps = {
}
render() {
return (
<Touchable />
)
}
}
```

View File

@@ -0,0 +1,70 @@
# TouchableWithoutFeedback
Do not use unless you have a very good reason. All the elements that respond to
press should have a visual feedback when touched. This is one of the primary
reason a "web" app doesn't feel "native".
**NOTE: `TouchableWithoutFeedback` supports only one child**. If you wish to have
several child components, wrap them in a View.
## Props
**accessibilityLabel**: string
Overrides the text that's read by the screen reader when the user interacts
with the element.
(web) **accessibilityRole**: oneOf(roles) = 'button'
Allows assistive technologies to present and support interaction with the view
**accessible**: bool = true
When `false`, the view is hidden from screenreaders.
**delayLongPress**: number
Delay in ms, from `onPressIn`, before `onLongPress` is called.
**delayPressIn**: number
Delay in ms, from the start of the touch, before `onPressIn` is called.
**delayPressOut**: number
Delay in ms, from the release of the touch, before `onPressOut` is called.
**disabled**: bool
If true, disable all interactions for this component.
**hitSlop**: `{top: number, left: number, bottom: number, right: number}`
This defines how far your touch can start away from the button. This is added
to `pressRetentionOffset` when moving off of the button. **NOTE**: The touch
area never extends past the parent view bounds and the z-index of sibling views
always takes precedence if a touch hits two overlapping views.
**onLayout**: function
Invoked on mount and layout changes with.
`{nativeEvent: {layout: {x, y, width, height}}}`
**onLongPress**: function
**onPress**: function
Called when the touch is released, but not if cancelled (e.g. by a scroll that steals the responder lock).
**onPressIn**: function
**onPressOut**: function
**pressRetentionOffset**: `{top: number, left: number, bottom: number, right: number}`
When the scroll view is disabled, this defines how far your touch may move off
of the button, before deactivating the button. Once deactivated, try moving it
back and you'll see that the button is once again reactivated! Move it back and
forth several times while the scroll view is disabled. Ensure you pass in a
constant to reduce memory allocations.

View File

@@ -1,24 +1,83 @@
# View
`View` is the fundamental UI building block. It is a component that supports
style, layout with flexbox, and accessibility controls. It can be nested
style, layout with flexbox, and accessibility controls. It can be nested
inside another `View` and has 0-to-many children of any type.
Also, refer to React Native's documentation about the [Gesture Responder
System](http://facebook.github.io/react-native/releases/0.22/docs/gesture-responder-system.html).
Unsupported React Native props:
`onAccessibilityTap`,
`hitSlop`,
`onMagicTap`
## Props
**accessibilityLabel** string
NOTE: `View` will transfer all other props to the rendered HTML element.
Overrides the text that's read by the screen reader when the user interacts
with the element. This is implemented using `aria-label`.
**accessibilityLabel**: string
**component** function, string
Defines the text available to assistive technologies upon interaction with the
element. (This is implemented using `aria-label`.)
Default is `div`.
**accessibilityLiveRegion**: oneOf('assertive', 'off', 'polite') = 'off'
**pointerEvents** oneOf('auto', 'box-only', 'box-none', 'none')
Indicates to assistive technologies whether to notify the user when the view
changes. The values of this attribute are expressed in degrees of importance.
When regions are specified as `polite` (recommended), updates take low
priority. When regions are specified as `assertive`, assistive technologies
will interrupt and immediately notify the user. (This is implemented using
[`aria-live`](http://www.w3.org/TR/wai-aria/states_and_properties#aria-live).)
We deviate from the CSS spec by supporting additional `pointerEvents` modes,
therefore `pointerEvents` is excluded from `style`.
(web) **accessibilityRole**: oneOf(roles)
Allows assistive technologies to present and support interaction with the view
in a manner that is consistent with user expectations for similar views of that
type. For example, marking a touchable view with an `accessibilityRole` of
`button`. (This is implemented using [ARIA roles](http://www.w3.org/TR/wai-aria/roles#role_definitions)).
Note: Avoid changing `accessibilityRole` values over time or after user
actions. Generally, accessibility APIs do not provide a means of notifying
assistive technologies of a `role` value change.
**accessible**: bool = true
When `false`, the view is hidden from assistive technologies. (This is
implemented using `aria-hidden`.)
**onLayout**: function
(TODO)
**onMoveShouldSetResponder**: function
**onMoveShouldSetResponderCapture**: function
**onResponderGrant**: function
For most touch interactions, you'll simply want to wrap your component in
`TouchableHighlight` or `TouchableOpacity`.
**onResponderMove**: function
**onResponderReject**: function
**onResponderRelease**: function
**onResponderTerminate**: function
**onResponderTerminationRequest**: function
**onStartShouldSetResponder**: function
**onStartShouldSetResponderCapture**: function
**pointerEvents**: oneOf('auto', 'box-only', 'box-none', 'none') = 'auto'
Configure the `pointerEvents` of the view. The enhanced `pointerEvents` modes
provided are not part of the CSS spec, therefore, `pointerEvents` is excluded
from `style`.
`box-none` is the equivalent of:
@@ -34,7 +93,7 @@ therefore `pointerEvents` is excluded from `style`.
.box-only * { pointer-events: none }
```
**style** style
**style**: style
+ `alignContent`
+ `alignItems`
@@ -55,6 +114,8 @@ therefore `pointerEvents` is excluded from `style`.
+ `bottom`
+ `boxShadow`
+ `boxSizing`
+ `cursor`
+ `flex` (number)
+ `flexBasis`
+ `flexDirection`
+ `flexGrow`
@@ -63,7 +124,13 @@ therefore `pointerEvents` is excluded from `style`.
+ `height`
+ `justifyContent`
+ `left`
+ `margin`
+ `margin` (single value)
+ `marginBottom`
+ `marginHorizontal`
+ `marginLeft`
+ `marginRight`
+ `marginTop`
+ `marginVertical`
+ `maxHeight`
+ `maxWidth`
+ `minHeight`
@@ -73,11 +140,18 @@ therefore `pointerEvents` is excluded from `style`.
+ `overflow`
+ `overflowX`
+ `overflowY`
+ `padding`
+ `padding` (single value)
+ `paddingBottom`
+ `paddingHorizontal`
+ `paddingLeft`
+ `paddingRight`
+ `paddingTop`
+ `paddingVertical`
+ `position`
+ `right`
+ `top`
+ `transform`
+ `transformMatrix`
+ `userSelect`
+ `visibility`
+ `width`
@@ -103,18 +177,16 @@ Default:
(See [facebook/css-layout](https://github.com/facebook/css-layout)).
**testID** string
**testID**: string
Used to locate this view in end-to-end tests.
## Examples
```js
import React, { View } from 'react-native-web'
import React, { Component, PropTypes, StyleSheet, View } from 'react-native'
const { Component, PropTypes } = React
class Example extends Component {
export default class ViewExample extends Component {
render() {
return (
<View style={styles.row}>
@@ -128,14 +200,12 @@ class Example extends Component {
}
}
const styles = {
const styles = StyleSheet.create({
row: {
flexDirection: 'row'
},
cell: {
flexGrow: 1
}
}
export default Example
})
```

View File

@@ -0,0 +1,33 @@
# Accessibility
On the Web, assistive technologies derive useful information about the
structure, purpose, and interactivity of apps from their [HTML
elements][html-accessibility-url], attributes, and [ARIA in
HTML][aria-in-html-url].
The most common and best supported accessibility features of the Web are
exposed as the props: `accessible`, `accessibilityLabel`,
`accessibilityLiveRegion`, and `accessibilityRole`.
React Native for Web does not provide a way to directly control the type of the
rendered HTML element. The `accessibilityRole` prop is used to infer an
[analogous HTML element][html-aria-url] to use in addition to the resulting
ARIA `role`, where possible. While this may contradict some ARIA
recommendations, it also helps avoid certain HTML5 conformance errors and
accessibility anti-patterns (e.g., giving a `heading` role to a `button`
element).
For example:
* `<View accessibilityRole='article' />` => `<article role='article' />`.
* `<View accessibilityRole='banner' />` => `<header role='banner' />`.
* `<View accessibilityRole='button' />` => `<button type='button' role='button' />`.
* `<Text accessibilityRole='link' href='/' />` => `<a role='link' href='/' />`.
* `<View accessibilityRole='main' />` => `<main role='main' />`.
Other ARIA properties should be set via [direct
manipulation](./direct-manipulation.md).
[aria-in-html-url]: https://w3c.github.io/aria-in-html/
[html-accessibility-url]: http://www.html5accessibility.com/
[html-aria-url]: http://www.w3.org/TR/html-aria/

View File

@@ -0,0 +1,115 @@
# Direct manipulation
It is sometimes necessary to make changes directly to a component without using
state/props to trigger a re-render of the entire subtree in the browser, this
is done by directly modifying a DOM node. `setNativeProps` is the React Native
equivalent to setting properties directly on a DOM node. Use direct
manipulation when frequent re-rendering creates a performance bottleneck Direct
manipulation will not be a tool that you reach for frequently.
## `setNativeProps` and `shouldComponentUpdate`
`setNativeProps` is imperative and stores state in the native layer (DOM,
UIView, etc.) and not within your React components, which makes your code more
difficult to reason about. Before you use it, try to solve your problem with
`setState` and `shouldComponentUpdate`.
## Avoiding conflicts with the render function
If you update a property that is also managed by the render function, you might
end up with some unpredictable and confusing bugs because anytime the component
re-renders and that property changes, whatever value was previously set from
`setNativeProps` will be completely ignored and overridden.
## Why use `setNativeProps` on Web?
Using `setNativeProps` in web-specific code is required when making changes to
`className` or `style`, as these properties are controlled by React Native for
Web and setting them directly may cause unintended rendering issues.
```js
setOpacityTo(value) {
this._childElement.setNativeProps({
style: { opacity: value }
})
}
```
## Composite components and `setNativeProps`
Composite components are not backed by a DOM node, so you cannot call
`setNativeProps` on them. Consider this example:
```js
const MyButton = (props) => (
<View>
<Text>{props.label}</Text>
</View>
)
const App = () => (
<TouchableOpacity>
<MyButton label="Press me!" />
</TouchableOpacity>
)
```
If you run this you will immediately see this error: `Touchable` child must
either be native or forward `setNativeProps` to a native component. This occurs
because `MyButton` isn't directly backed by a native view whose opacity should
be set. You can think about it like this: if you define a component with
`React.Component/createClass` you would not expect to be able to set a style
prop on it and have that work - you would need to pass the style prop down to a
child, unless you are wrapping a native component. Similarly, we are going to
forward `setNativeProps` to a native-backed child component.
## Forward `setNativeProps` to a child
All we need to do is provide a `setNativeProps` method on our component that
calls `setNativeProps` on the appropriate child with the given arguments.
```js
class MyButton extends React.Component {
setNativeProps(nativeProps) {
this._root.setNativeProps(nativeProps)
}
render() {
return (
<View ref={component => this._root = component}>
<Text>{this.props.label}</Text>
</View>
)
}
}
```
You can now use `MyButton` inside of `TouchableOpacity`!
## `setNativeProps` to clear `TextInput` value
Another very common use case of `setNativeProps` is to clear the value of a
`TextInput`. For example, the following code demonstrates clearing the input
when you tap a button:
```js
class App extends React.Component {
_handlePress() {
this._textInput.setNativeProps({ text: '' })
}
render() {
return (
<View style={styles.container}>
<TextInput
ref={component => this._textInput = component}
style={styles.textInput}
/>
<TouchableOpacity onPress={this._handlePress.bind(this)}>
<Text>Clear text</Text>
</TouchableOpacity>
</View>
)
}
}
```

View File

@@ -0,0 +1,23 @@
# Known issues
## Missing modules and Views
This is an initial release of React Native for Web, therefore, not all of the
views present on iOS/Android are released on Web. We are very much interested in
the community's feedback on the next set of modules and views.
Not all the modules or views for iOS/Android can be implemented on Web. In some
cases it will be necessary to use a Web counterpart or to guard the use of a
module with `Platform.OS` (e.g. `NativeModules`)
## Missing component props
There are properties that do not work across all platforms. All web-specific
props are annotated with `(web)` in the documentation.
## Platform parity
There are some known issues in React Native where APIs could be made more
consistent between platforms. For example, React Native for Web includes
`ActivityIndicator` and a horizontal `ProgressBar` for Web use, in anticipation
of the convergence between the iOS and Android components in React Native.

View File

@@ -0,0 +1,78 @@
# React Native
Use a module loader that supports package aliases (e.g., webpack), and alias
`react-native` to `react-native-web`.
```js
// webpack.config.js
module.exports = {
// ...
resolve: {
alias: {
'react-native': 'react-native-web'
}
}
}
```
## Image assets
In order to require image assets (e.g. `require('assets/myimage.png')`), add
the `url-loader` to the webpack config:
```js
// webpack.config.js
module.exports = {
// ...
module: {
loaders: {
test: /\.(gif|jpe?g|png|svg)$/,
loader: 'url-loader',
query: { name: '[name].[hash:16].[ext]' }
}
}
```
## Web-specific code
Minor platform differences can use the `Platform` module.
```js
import { AppRegistry, Platform, StyleSheet } from 'react-native'
const styles = StyleSheet.create({
height: (Platform.OS === 'web') ? 200 : 100
})
AppRegistry.registerComponent('MyApp', () => MyApp)
if (Platform.OS === 'web') {
AppRegistry.runApplication('MyApp', {
rootTag: document.getElementById('react-root')
});
}
```
More substantial Web-specific implementation code should be written in files
with the extension `.web.js`, which webpack will automatically resolve.
## Optimizations
Production builds can benefit from dead-code elimination by defining the
following variables:
```js
// webpack.config.js
module.exports = {
// ...
plugins: [
new webpack.DefinePlugin({
'Platform.OS': 'web',
'process.env.NODE_ENV': JSON.stringify('production')
})
}
}
```

97
docs/guides/rendering.md Normal file
View File

@@ -0,0 +1,97 @@
# Client and Server rendering
It's recommended that you use a module loader that supports package aliases
(e.g., webpack), and alias `react-native` to `react-native-web`.
```js
// webpack.config.js
module.exports = {
// ...other configuration
resolve: {
alias: {
'react-native': 'react-native-web'
}
}
}
```
## Client-side rendering
Rendering without using the `AppRegistry`:
```js
import React from 'react-native'
// DOM render
React.render(<div />, document.getElementById('react-app'))
// Server render
React.renderToString(<div />)
React.renderToStaticMarkup(<div />)
```
Rendering using the `AppRegistry`:
```
// App.js
import React, { AppRegistry } from 'react-native'
// component that renders the app
const AppContainer = (props) => { /* ... */ }
export default AppContainer
```
```js
// client.js
import React, { AppRegistry } from 'react-native'
import App from './App'
// registers the app
AppRegistry.registerComponent('App', () => App)
// mounts and runs the app within the `rootTag` DOM node
AppRegistry.runApplication('App', { initialProps, rootTag: document.getElementById('react-app') })
```
React Native for Web extends `AppRegistry` to provide support for server-side
rendering.
```js
// AppShell.js
import React from 'react-native'
const AppShell = (html, style) => (
<html>
<head>
<meta charSet="utf-8" />
<meta content="initial-scale=1,width=device-width" name="viewport" />
{style}
</head>
<body>
<div id="react-app" dangerouslySetInnerHTML={{ __html: html }} />
</body>
</html>
)
export default AppShell
```
```js
// server.js
import React, { AppRegistry } from 'react-native'
import App from './App'
import AppShell from './AppShell'
// registers the app
AppRegistry.registerComponent('App', () => App)
// prerenders the app
const { html, style } = AppRegistry.prerenderApplication('App', { initialProps })
// renders the full-page markup
const renderedApplicationHTML = React.renderToString(<AppShell html={html} style={style} />)
```

225
docs/guides/style.md Normal file
View File

@@ -0,0 +1,225 @@
# Style
React Native for Web relies on JavaScript to define styles for your
application. Along with a novel JS-to-CSS conversion strategy, this allows you
to avoid issues arising from the [7 deadly sins of
CSS](https://speakerdeck.com/vjeux/react-css-in-js):
1. Global namespace
2. Dependency hell
3. No dead code elimination
4. No code minification
5. No sharing of constants
6. Non-deterministic resolution
7. Lack of isolation
## Defining styles
Styles should be defined outside of the component:
```js
class Example extends React.Component {}
const styles = StyleSheet.create({
heading: {
color: 'gray',
fontSize: '2rem'
},
text: {
color: 'gray',
fontSize: '1.25rem'
}
})
```
Using `StyleSheet.create` is optional but provides some key advantages: styles
are immutable in development, styles are converted to CSS rather than applied
as inline styles, and styles are only created once for the application and not
on every render.
The attribute names and values are a subset of CSS. See the `style`
documentation of individual components.
## Using styles
All the React Native components accept a `style` attribute.
```js
<Text style={styles.text} />
<View style={styles.view} />
```
A common pattern is to conditionally add style based on a condition:
```js
// either
<View style={[
styles.base,
this.state.active && styles.active
]} />
// or
<View style={{
...styles.base,
...(this.state.active && styles.active)
}} />
```
## Composing styles
In order to let a call site customize the style of your component children, you
can pass styles around. Use `View.propTypes.style` and `Text.propTypes.style` in
order to make sure only valid styles are being passed.
```js
class List extends React.Component {
static propTypes = {
style: View.propTypes.style,
elementStyle: View.propTypes.style,
}
render() {
return (
<View style={this.props.style}>
{elements.map((element) =>
<View style={[ styles.element, this.props.elementStyle ]} />
)}
</View>
);
}
}
```
In another file:
```js
<List style={styles.list} elementStyle={styles.listElement} />
```
You also have much greater control over how styles are composed when compared
to using class names. For example, you may choose to accept a limited subset
of style props in the component's API, and control when they are applied:
```js
class List extends React.Component {
static propTypes = {
children: React.PropTypes.any,
// limit which styles are accepted
style: React.PropTypes.shape({
borderColor: View.propTypes.borderColor,
borderWidth: View.propTypes.borderWidth
})
}
render() {
return (
<View
children={children}
style={[
this.props.style,
// override border-color when scrolling
isScrolling && { borderColor: 'transparent' }
]}
/>
)
}
}
```
## Media Queries
`StyleSheet.create` is a way of defining the styles your application requires;
it does not concern itself with _where_ or _when_ those styles are applied to
elements.
There are various React libraries wrapping JavaScript Media Query API's, e.g.,
[react-media-queries](https://github.com/bloodyowl/react-media-queries),
[media-query-fascade](https://github.com/tanem/media-query-facade), or
[react-responsive](https://github.com/contra/react-responsive). This has the
benefit of co-locating breakpoint-specific DOM and style changes.
## Pseudo-classes and pseudo-elements
Pseudo-classes like `:hover` and `:focus` can be implemented with the events
(e.g. `onFocus`). Pseudo-elements are not supported; elements should be used
instead.
## How it works
Every call to `StyleSheet.create` extracts the unique _declarations_ and
converts them to a unique CSS rule. This is sometimes referred to as "atomic
CSS". All the core components map their `style` property-value pairs to the
corresponding `className`'s.
By doing this, the total size of the generated CSS is determined by the
total number of unique declarations (rather than the total number of rules in
the application), making it viable to inline the style sheet when pre-rendering
on the server. Styles are updated if new module bundle are loaded asynchronously.
JavaScript definition:
```js
const styles = StyleSheet.create({
heading: {
color: 'gray',
fontSize: '2rem'
},
text: {
color: 'gray',
fontSize: '1.25rem'
}
})
```
CSS output:
```css
._s1 { color: gray; }
._s2 { font-size: 2rem; }
._s3 { font-size: 1.25rem; }
```
Rendered HTML:
```html
<span className="_s1 _s2">Heading</span>
<span className="_s1 _s3">Text</span>
```
### Reset
You **do not** need to include a CSS reset or
[normalize.css](https://necolas.github.io/normalize.css/).
React Native for Web includes a very small CSS reset taken from normalize.css.
It removes unwanted User Agent styles from (pseudo-)elements beyond the reach
of React (e.g., `html`, `body`) or inline styles (e.g., `::-moz-focus-inner`).
```css
html {
font-family: sans-serif;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
}
body {
margin: 0;
}
button::-moz-focus-inner,
input::-moz-focus-inner {
border: 0;
padding: 0;
}
input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
ol,
ul,
li {
list-style:none
}
```

View File

@@ -1,123 +0,0 @@
# Styling strategy
Using the `style` attribute would normally produce inline styles. There are
several existing approaches to using the `style` attribute, some of which
convert inline styles to static CSS:
[jsxstyle](https://github.com/petehunt/jsxstyle),
[react-free-style](https://github.com/blakeembrey/react-free-style/),
[react-inline](https://github.com/martinandert/react-inline),
[react-native](https://facebook.github.io/react-native/),
[react-style](https://github.com/js-next/react-style),
[stilr](https://github.com/kodyl/stilr).
## Style syntax: native vs proprietary data structure
React Native for Web diverges from React Native by using plain JS objects for
styles:
```js
<Text style={styles.root}>...</Text>
const styles = {
root: {
background: 'transparent',
display: 'flex',
flexGrow: 1,
justifyContent: 'center'
}
};
```
Most approaches to managing style in React introduce a proprietary data
structure, often via an implementation of `Stylesheet.create`.
```js
<Text style={styles.root}>...</Text>
const styles = Stylesheet.create({
root: {
background: 'transparent',
display: 'flex',
flexGrow: 1,
justifyContent: 'center'
}
});
```
## JS-to-CSS: conversion strategies
Mapping entire `style` objects to CSS rules can lead to increasingly large CSS
files. Each new component adds new rules to the stylesheet.
![](../static/styling-strategy.png)
One strategy for converting styles from JS to CSS is to map style objects to
CSS rules. Another strategy is to map declarations to declarations.
React Native for Web currently includes a proof-of-concept implementation of
the latter strategy. This results in smaller CSS files because all applications
has fewer unique declarations than total declarations. Creating a new component
with no new unique declarations results in no change to the CSS file.
For example:
```js
<Text style={styles.root}>...</Text>
const styles = {
root: {
background: 'transparent',
display: 'flex',
flexGrow: 1,
justifyContent: 'center'
}
};
```
Yields:
```html
<span className="_abcde _fghij _klmno _pqrst">...</span>
```
And is backed by:
```css
._abcde { background: transparent }
._fghij { display: flex }
._klmno { flex-grow: 1 }
._pqrst { justify-content: center }
```
The current implementation uses a precomputed CSS library of single-declaration
rules, with obfuscated selectors. This handles a signficant portion of possible
declarations. A build-time implementation would produce more accurate CSS
files and fall through to inline styles significantly less often.
(CSS libraries like [Atomic CSS](http://acss.io/),
[Basscss](http://www.basscss.com/), [SUIT CSS](https://suitcss.github.io/), and
[tachyons](http://tachyons.io/) are attempts to limit style scope and limit
stylesheet growth in a similar way. But they're CSS utility libraries, each with a
particular set of classes and features to learn. All of them require developers
to manually connect CSS classes for given styles.)
## Dynamic styles: use inline styles
Some styles cannot be resolved ahead of time and continue to rely on inline
styles:
```js
<View style={{ backgroundColor: (Math.random() > 0.5 ? 'red' : 'black') }}>...</Text>
```
## Media Queries, pseudo-classes, and pseudo-elements
Media Queries could be replaced with `mediaMatch`. This would have the added
benefit of co-locating breakpoint-specific DOM and style changes. Perhaps Media
Query data could be accessed on `this.content`?
Pseudo-classes like `:hover` and `:focus` can be handled with JavaScript.
Pseudo-elements should be avoided in general, but for particular cases like
`::placeholder` it might be necessary to reimplement it in the `TextInput`
component (see React Native's API).

View File

@@ -1,19 +0,0 @@
module.exports = {
entry: {
example: './example.js'
},
module: {
loaders: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: 'babel-loader',
query: { cacheDirectory: true }
}
]
},
output: {
filename: 'example.js',
path: '../dist'
}
}

View File

@@ -1,62 +1,43 @@
import React, { Image, Swipeable, Text, TextInput, Touchable, View } from '../dist/react-native-web'
import GridView from './GridView'
import Heading from './Heading'
import MediaQueryWidget from './MediaQueryWidget'
import React, { Image, StyleSheet, ScrollView, Text, TextInput, TouchableHighlight, View } from 'react-native'
const { Component, PropTypes } = React
class Heading extends Component {
static propTypes = {
children: Text.propTypes.children,
level: PropTypes.oneOf(['1', '2', '3']),
size: PropTypes.oneOf(['xlarge', 'large', 'normal'])
}
static defaultProps = {
level: '1',
size: 'normal'
}
render() {
const { children, level, size } = this.props
return (
<Text
children={children}
component={`h${level}`}
style={headingStyles.size[size]}
/>
)
}
}
const headingStyles = {
size: {
xlarge: {
fontSize: '2rem',
marginBottom: '1em'
},
large: {
fontSize: '1.5rem',
marginBottom: '1em',
marginTop: '1em'
},
normal: {
fontSize: '1.25rem',
marginBottom: '0.5em',
marginTop: '0.5em'
}
}
}
class Example extends Component {
export default class App extends React.Component {
static propTypes = {
mediaQuery: React.PropTypes.object,
style: View.propTypes.style
}
render() {
return (
<View style={styles.root}>
<Heading level='1' size='xlarge'>React Native for Web: examples</Heading>
constructor(...args) {
super(...args)
this.state = {
scrollEnabled: true
}
}
<Heading level='2' size='large'>Image</Heading>
render() {
const { mediaQuery } = this.props
const finalRootStyles = [
rootStyles.common,
mediaQuery.small.matches && rootStyles.mqSmall,
mediaQuery.large.matches && rootStyles.mqLarge
]
return (
<ScrollView accessibilityRole='main'>
<View style={finalRootStyles}>
<Heading size='xlarge'>React Native for Web</Heading>
<Text>React Native Web takes the core components from <Text
accessibilityRole='link' href='https://facebook.github.io/react-native/'>React
Native</Text> and brings them to the web. These components provide
simple building blocks touch handling, flexbox layout,
scroll views from which more complex components and apps can be
constructed.</Text>
<MediaQueryWidget mediaQuery={mediaQuery} />
<Heading size='large'>Image</Heading>
<Image
accessibilityLabel='accessible image'
children={<Text>Inner content</Text>}
@@ -79,22 +60,7 @@ class Example extends Component {
testID='Example.image'
/>
<Heading level='2' size='large'>Swipeable</Heading>
<Swipeable
onSwiped={(e) => { console.log('Swipeable.onSwiped', e) }}
testID={'Example.swipeable'}
>
<View
style={{
backgroundColor: 'black',
alignSelf: 'center',
width: '200px',
height: '200px'
}}
/>
</Swipeable>
<Heading level='2' size='large'>Text</Heading>
<Heading size='large'>Text</Heading>
<Text
onPress={(e) => { console.log('Text.onPress', e) }}
testID={'Example.text'}
@@ -119,23 +85,31 @@ class Example extends Component {
hendrerit consequat.
</Text>
<Heading level='2' size='large'>TextInput</Heading>
<Heading size='large'>TextInput</Heading>
<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={true} />
<TextInput secureTextEntry />
<TextInput defaultValue='read only' editable={false} />
<TextInput keyboardType='email-address' placeholder='you@domain.com' placeholderTextColor='red' />
<TextInput keyboardType='numeric' />
<TextInput keyboardType='tel' />
<TextInput keyboardType='url' />
<TextInput keyboardType='search' />
<TextInput defaultValue='default value' multiline />
<TextInput keyboardType='phone-pad' />
<TextInput defaultValue='https://delete-me' keyboardType='url' placeholder='https://www.some-website.com' selectTextOnFocus />
<TextInput
defaultValue='default value'
maxNumberOfLines={10}
multiline
numberOfLines={5}
/>
<Heading level='2' size='large'>Touchable</Heading>
<Touchable
<Heading size='large'>Touchable</Heading>
<TouchableHighlight
accessibilityLabel={'Touchable element'}
activeHighlight='lightblue'
activeOpacity={0.8}
onLongPress={(e) => { console.log('Touchable.onLongPress', e) }}
@@ -146,10 +120,10 @@ class Example extends Component {
<View style={styles.touchableArea}>
<Text>Touchable area (press, long press)</Text>
</View>
</Touchable>
</TouchableHighlight>
<Heading level='2' size='large'>View</Heading>
<Heading level='3'>Default layout</Heading>
<Heading size='large'>View</Heading>
<Heading>Default layout</Heading>
<View>
{[ 1, 2, 3, 4, 5, 6 ].map((item, i) => {
return (
@@ -160,7 +134,7 @@ class Example extends Component {
})}
</View>
<Heading level='3'>Row layout</Heading>
<Heading>Row layout</Heading>
<View style={styles.row}>
{[ 1, 2, 3, 4, 5, 6 ].map((item, i) => {
return (
@@ -171,13 +145,13 @@ class Example extends Component {
})}
</View>
<Heading level='3'>pointerEvents</Heading>
<View style={styles.row}>
<Heading>pointerEvents</Heading>
<GridView alley='10px'>
{['box-none', 'box-only', 'none'].map((value, i) => {
return (
<View
accessibilityRole='link'
children={value}
component='a'
href='https://google.com'
key={i}
pointerEvents={value}
@@ -185,18 +159,73 @@ class Example extends Component {
/>
)
})}
</GridView>
<Heading size='large'>ScrollView</Heading>
<label>
<input
checked={this.state.scrollEnabled}
onChange={() => this.setState({
scrollEnabled: !this.state.scrollEnabled
})}
type='checkbox'
/> Enable scroll
</label>
<Heading>Default layout</Heading>
<View style={styles.scrollViewContainer}>
<ScrollView
contentContainerStyle={styles.scrollViewContentContainerStyle}
onScroll={e => console.log('ScrollView.onScroll', e)}
scrollEnabled={this.state.scrollEnabled}
scrollEventThrottle={1} // 1 event per second
style={styles.scrollViewStyle}
>
{Array.from({ length: 50 }).map((item, i) => (
<View key={i} style={styles.box}>
<Text>{i}</Text>
</View>
))}
</ScrollView>
</View>
</View>
<Heading>Horizontal layout</Heading>
<View style={styles.scrollViewContainer}>
<ScrollView
contentContainerStyle={styles.scrollViewContentContainerStyle}
horizontal
onScroll={e => console.log('ScrollView.onScroll', e)}
scrollEnabled={this.state.scrollEnabled}
scrollEventThrottle={1} // 1 event per second
style={styles.scrollViewStyle}
>
{Array.from({ length: 50 }).map((item, i) => (
<View key={i} style={[ styles.box, styles.horizontalBox ]}>
<Text>{i}</Text>
</View>
))}
</ScrollView>
</View>
</View>
</ScrollView>
)
}
}
const styles = {
root: {
fontFamily: 'sans-serif',
maxWidth: '600px',
margin: '0 auto'
const rootStyles = StyleSheet.create({
common: {
marginVertical: 0,
marginHorizontal: 'auto'
},
mqSmall: {
maxWidth: '400px'
},
mqLarge: {
maxWidth: '600px'
}
})
const styles = StyleSheet.create({
row: {
flexDirection: 'row',
flexWrap: 'wrap'
@@ -205,7 +234,10 @@ const styles = {
alignItems: 'center',
flexGrow: 1,
justifyContent: 'center',
borderWidth: '1px'
borderWidth: 1
},
horizontalBox: {
width: '50px'
},
boxFull: {
width: '100%'
@@ -213,7 +245,7 @@ const styles = {
pointerEventsBox: {
alignItems: 'center',
borderWidth: '1px',
flexGrow: '1',
flexGrow: 1,
height: '100px',
justifyContent: 'center'
},
@@ -222,7 +254,14 @@ const styles = {
borderWidth: 1,
height: '200px',
justifyContent: 'center'
},
scrollViewContainer: {
height: '200px'
},
scrollViewStyle: {
borderWidth: '1px'
},
scrollViewContentContainerStyle: {
padding: '10px'
}
}
React.render(<Example />, document.getElementById('react-root'))
})

View File

@@ -0,0 +1,65 @@
import React, { Component, PropTypes, StyleSheet, View } from 'react-native'
export default class GridView extends Component {
static propTypes = {
alley: PropTypes.string,
children: PropTypes.oneOfType([
PropTypes.element,
PropTypes.arrayOf(PropTypes.element)
]),
gutter: PropTypes.string,
style: PropTypes.object
}
static defaultProps = {
alley: '0',
gutter: '0'
}
render() {
const { alley, children, gutter, style, ...other } = this.props
const rootStyle = {
...style,
...styles.root
}
const contentContainerStyle = {
...styles.contentContainer,
marginHorizontal: `calc(-0.5 * ${alley})`,
paddingHorizontal: `${gutter}`
}
const newChildren = React.Children.map(children, (child) => {
return child && React.cloneElement(child, {
style: {
...child.props.style,
...styles.column,
marginHorizontal: `calc(0.5 * ${alley})`
}
})
})
return (
<View className='GridView' {...other} style={rootStyle}>
<View style={contentContainerStyle}>
{newChildren}
</View>
</View>
)
}
}
const styles = StyleSheet.create({
root: {
overflow: 'hidden'
},
contentContainer: {
flexDirection: 'row',
flexGrow: 1
},
// distribute all space (rather than extra space)
column: {
flexBasis: '0%'
}
})

View File

@@ -0,0 +1,34 @@
import React, { StyleSheet, Text } from 'react-native'
const Heading = ({ children, size = 'normal' }) => (
<Text
accessibilityRole='heading'
children={children}
style={{ ...styles.root, ...sizeStyles[size] }}
/>
)
const sizeStyles = StyleSheet.create({
xlarge: {
fontSize: '2rem',
marginBottom: '1em'
},
large: {
fontSize: '1.5rem',
marginBottom: '1em',
marginTop: '1em'
},
normal: {
fontSize: '1.25rem',
marginBottom: '0.5em',
marginTop: '0.5em'
}
})
const styles = StyleSheet.create({
root: {
fontFamily: '"Helvetica Neue", arial, sans-serif'
}
})
export default Heading

View File

@@ -0,0 +1,39 @@
import React, { StyleSheet, Text, View } from 'react-native'
const MediaQueryWidget = ({ mediaQuery = {} }) => {
const active = Object.keys(mediaQuery).reduce((active, alias) => {
if (mediaQuery[alias].matches) {
active = {
alias,
mql: mediaQuery[alias]
}
}
return active
}, {})
return (
<View style={styles.root}>
<Text style={styles.heading}>Active Media Query</Text>
<Text style={styles.text}>{`"${active.alias}"`} {active.mql && active.mql.media}</Text>
</View>
)
}
const styles = StyleSheet.create({
root: {
alignItems: 'center',
borderWidth: 1,
marginVertical: 10,
padding: 10
},
heading: {
fontWeight: 'bold',
padding: 5,
textAlign: 'center'
},
text: {
textAlign: 'center'
}
})
export default MediaQueryWidget

View File

@@ -1,5 +1,6 @@
<!DOCTYPE html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>React Native for Web</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<div id="react-root"></div>
<script src="../dist/example.js"></script>
<script src="/examples.js"></script>

23
examples/index.js Normal file
View File

@@ -0,0 +1,23 @@
import { MediaProvider, matchMedia } from 'react-media-queries'
import App from './components/App'
import createGetter from 'react-media-queries/lib/createMediaQueryGetter'
import createListener from 'react-media-queries/lib/createMediaQueryListener'
import React, { AppRegistry } from 'react-native'
const mediaQueries = {
small: '(min-width: 300px)',
medium: '(min-width: 400px)',
large: '(min-width: 500px)'
}
const ResponsiveApp = matchMedia()(App)
const WrappedApp = () => (
<MediaProvider getMedia={createGetter(mediaQueries)} listener={createListener(mediaQueries)}>
<ResponsiveApp />
</MediaProvider>
)
AppRegistry.registerComponent('Example', () => WrappedApp)
AppRegistry.runApplication('Example', {
rootTag: document.getElementById('react-root')
})

View File

@@ -1,57 +1,76 @@
{
"name": "react-native-web",
"version": "0.0.5",
"version": "0.0.18",
"description": "React Native for Web",
"main": "dist/react-native-web.js",
"main": "dist/index.js",
"files": [
"dist"
],
"scripts": {
"prepublish": "NODE_ENV=production npm run build",
"build": "rm -rf ./dist && webpack --config config/webpack.config.js --sort-assets-by --progress",
"example": "cd example && webpack --config webpack.config.js",
"lint": "eslint .",
"specs": "NODE_ENV=test karma start config/karma.config.js",
"specs:watch": "npm run specs -- --no-single-run",
"start": "webpack-dev-server --config config/webpack.config.js --inline --hot --colors --quiet",
"test": "npm run specs && npm run lint"
"build": "rm -rf ./dist && mkdir dist && babel src -d dist --ignore **/__tests__,src/modules/specHelpers",
"build:umd": "webpack --config config/webpack.config.js --sort-assets-by --progress",
"examples": "webpack-dev-server --config config/webpack.config.example.js --inline --hot --colors --quiet",
"lint": "eslint config src",
"prepublish": "npm run build && npm run build:umd",
"test": "karma start config/karma.config.js",
"test:watch": "npm run test -- --no-single-run"
},
"dependencies": {
"react": "^0.13.3",
"react-swipeable": "^3.0.2",
"react-tappable": "^0.6.0"
"fbjs": "^0.7.2",
"inline-style-prefix-all": "^1.0.3",
"lodash.debounce": "^4.0.3",
"react-textarea-autosize": "^3.1.0",
"react-timer-mixin": "^0.13.3"
},
"devDependencies": {
"autoprefixer-core": "^5.2.1",
"babel-core": "^5.8.23",
"babel-eslint": "^4.1.1",
"babel-loader": "^5.3.2",
"babel-runtime": "^5.8.20",
"css-loader": "^0.17.0",
"eslint": "^1.3.1",
"eslint-config-standard": "^4.3.1",
"eslint-config-standard-react": "^1.0.4",
"eslint-plugin-react": "^3.3.1",
"eslint-plugin-standard": "^1.3.0",
"extract-text-webpack-plugin": "^0.8.2",
"karma": "^0.13.9",
"karma-chrome-launcher": "^0.2.0",
"karma-firefox-launcher": "^0.1.6",
"karma-mocha": "^0.2.0",
"karma-sourcemap-loader": "^0.3.5",
"babel-cli": "^6.3.17",
"babel-core": "^6.3.13",
"babel-eslint": "^4.1.6",
"babel-loader": "^6.2.0",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-preset-es2015": "^6.3.13",
"babel-preset-react": "^6.3.13",
"babel-preset-stage-1": "^6.3.13",
"babel-runtime": "^6.3.19",
"eslint": "^1.10.3",
"eslint-config-standard": "^4.4.0",
"eslint-config-standard-react": "^1.2.1",
"eslint-plugin-react": "^3.13.1",
"eslint-plugin-standard": "^1.3.1",
"karma": "^0.13.16",
"karma-browserstack-launcher": "^0.1.8",
"karma-chrome-launcher": "^0.2.2",
"karma-firefox-launcher": "^0.1.7",
"karma-mocha": "^0.2.1",
"karma-sourcemap-loader": "^0.3.6",
"karma-spec-reporter": "0.0.23",
"karma-webpack": "^1.7.0",
"mocha": "^2.3.0",
"node-libs-browser": "^0.5.2",
"object-assign": "^4.0.1",
"postcss-loader": "^0.4.4",
"style-loader": "^0.12.3",
"webpack": "^1.12.1",
"webpack-dev-server": "^1.10.1"
"mocha": "^2.3.4",
"node-libs-browser": "^0.5.3",
"react": "^0.14.3",
"react-addons-test-utils": "^0.14.3",
"react-dom": "^0.14.3",
"react-media-queries": "^2.0.1",
"webpack": "^1.12.9",
"webpack-dev-server": "^1.14.0"
},
"peerDependencies": {
"react": "^0.14.3",
"react-dom": "^0.14.3"
},
"author": "Nicolas Gallagher",
"license": "MIT",
"license": "BSD-3-Clause",
"repository": {
"type": "git",
"url": "git://github.com/necolas/react-native-web.git"
}
},
"tags": [
"react"
],
"keywords": [
"react",
"react-component",
"react-native",
"web"
]
}

View File

@@ -0,0 +1,23 @@
/* eslint-env mocha */
import assert from 'assert'
import React from '..'
suite('ReactNativeWeb', () => {
suite('exports', () => {
test('React', () => {
assert.ok(React)
})
test('ReactDOM methods', () => {
assert.ok(React.findDOMNode)
assert.ok(React.render)
assert.ok(React.unmountComponentAtNode)
})
test('ReactDOM/server methods', () => {
assert.ok(React.renderToString)
assert.ok(React.renderToStaticMarkup)
})
})
})

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,288 @@
/* eslint-disable */
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule Interpolation
* @flow
*/
/* eslint no-bitwise: 0 */
'use strict';
/* @edit start */
var normalizeColor = require('../StyleSheet/normalizeColor');
var invariant = require('fbjs/lib/invariant');
/* @edit end */
type ExtrapolateType = 'extend' | 'identity' | 'clamp';
export type InterpolationConfigType = {
inputRange: Array<number>;
outputRange: (Array<number> | Array<string>);
easing?: ((input: number) => number);
extrapolate?: ExtrapolateType;
extrapolateLeft?: ExtrapolateType;
extrapolateRight?: ExtrapolateType;
};
var linear = (t) => t;
/**
* Very handy helper to map input ranges to output ranges with an easing
* function and custom behavior outside of the ranges.
*/
class Interpolation {
static create(config: InterpolationConfigType): (input: number) => number | string {
if (config.outputRange && typeof config.outputRange[0] === 'string') {
return createInterpolationFromStringOutputRange(config);
}
var outputRange: Array<number> = (config.outputRange: any);
checkInfiniteRange('outputRange', outputRange);
var inputRange = config.inputRange;
checkInfiniteRange('inputRange', inputRange);
checkValidInputRange(inputRange);
invariant(
inputRange.length === outputRange.length,
'inputRange (' + inputRange.length + ') and outputRange (' +
outputRange.length + ') must have the same length'
);
var easing = config.easing || linear;
var extrapolateLeft: ExtrapolateType = 'extend';
if (config.extrapolateLeft !== undefined) {
extrapolateLeft = config.extrapolateLeft;
} else if (config.extrapolate !== undefined) {
extrapolateLeft = config.extrapolate;
}
var extrapolateRight: ExtrapolateType = 'extend';
if (config.extrapolateRight !== undefined) {
extrapolateRight = config.extrapolateRight;
} else if (config.extrapolate !== undefined) {
extrapolateRight = config.extrapolate;
}
return (input) => {
invariant(
typeof input === 'number',
'Cannot interpolation an input which is not a number'
);
var range = findRange(input, inputRange);
return interpolate(
input,
inputRange[range],
inputRange[range + 1],
outputRange[range],
outputRange[range + 1],
easing,
extrapolateLeft,
extrapolateRight,
);
};
}
}
function interpolate(
input: number,
inputMin: number,
inputMax: number,
outputMin: number,
outputMax: number,
easing: ((input: number) => number),
extrapolateLeft: ExtrapolateType,
extrapolateRight: ExtrapolateType,
) {
var result = input;
// Extrapolate
if (result < inputMin) {
if (extrapolateLeft === 'identity') {
return result;
} else if (extrapolateLeft === 'clamp') {
result = inputMin;
} else if (extrapolateLeft === 'extend') {
// noop
}
}
if (result > inputMax) {
if (extrapolateRight === 'identity') {
return result;
} else if (extrapolateRight === 'clamp') {
result = inputMax;
} else if (extrapolateRight === 'extend') {
// noop
}
}
if (outputMin === outputMax) {
return outputMin;
}
if (inputMin === inputMax) {
if (input <= inputMin) {
return outputMin;
}
return outputMax;
}
// Input Range
if (inputMin === -Infinity) {
result = -result;
} else if (inputMax === Infinity) {
result = result - inputMin;
} else {
result = (result - inputMin) / (inputMax - inputMin);
}
// Easing
result = easing(result);
// Output Range
if (outputMin === -Infinity) {
result = -result;
} else if (outputMax === Infinity) {
result = result + outputMin;
} else {
result = result * (outputMax - outputMin) + outputMin;
}
return result;
}
function colorToRgba(input: string): string {
var int32Color = normalizeColor(input);
if (int32Color === null) {
return input;
}
int32Color = int32Color || 0; // $FlowIssue
var r = (int32Color & 0xff000000) >>> 24;
var g = (int32Color & 0x00ff0000) >>> 16;
var b = (int32Color & 0x0000ff00) >>> 8;
var a = (int32Color & 0x000000ff) / 255;
return `rgba(${r}, ${g}, ${b}, ${a})`;
}
var stringShapeRegex = /[0-9\.-]+/g;
/**
* Supports string shapes by extracting numbers so new values can be computed,
* and recombines those values into new strings of the same shape. Supports
* things like:
*
* rgba(123, 42, 99, 0.36) // colors
* -45deg // values with units
*/
function createInterpolationFromStringOutputRange(
config: InterpolationConfigType,
): (input: number) => string {
var outputRange: Array<string> = (config.outputRange: any);
invariant(outputRange.length >= 2, 'Bad output range');
outputRange = outputRange.map(colorToRgba);
checkPattern(outputRange);
// ['rgba(0, 100, 200, 0)', 'rgba(50, 150, 250, 0.5)']
// ->
// [
// [0, 50],
// [100, 150],
// [200, 250],
// [0, 0.5],
// ]
/* $FlowFixMe(>=0.18.0): `outputRange[0].match()` can return `null`. Need to
* guard against this possibility.
*/
var outputRanges = outputRange[0].match(stringShapeRegex).map(() => []);
outputRange.forEach(value => {
/* $FlowFixMe(>=0.18.0): `value.match()` can return `null`. Need to guard
* against this possibility.
*/
value.match(stringShapeRegex).forEach((number, i) => {
outputRanges[i].push(+number);
});
});
/* $FlowFixMe(>=0.18.0): `outputRange[0].match()` can return `null`. Need to
* guard against this possibility.
*/
var interpolations = outputRange[0].match(stringShapeRegex).map((value, i) => {
return Interpolation.create({
...config,
outputRange: outputRanges[i],
});
});
return (input) => {
var i = 0;
// 'rgba(0, 100, 200, 0)'
// ->
// 'rgba(${interpolations[0](input)}, ${interpolations[1](input)}, ...'
return outputRange[0].replace(stringShapeRegex, () => {
return String(interpolations[i++](input));
});
};
}
function checkPattern(arr: Array<string>) {
var pattern = arr[0].replace(stringShapeRegex, '');
for (var i = 1; i < arr.length; ++i) {
invariant(
pattern === arr[i].replace(stringShapeRegex, ''),
'invalid pattern ' + arr[0] + ' and ' + arr[i],
);
}
}
function findRange(input: number, inputRange: Array<number>) {
for (var i = 1; i < inputRange.length - 1; ++i) {
if (inputRange[i] >= input) {
break;
}
}
return i - 1;
}
function checkValidInputRange(arr: Array<number>) {
invariant(arr.length >= 2, 'inputRange must have at least 2 elements');
for (var i = 1; i < arr.length; ++i) {
invariant(
arr[i] >= arr[i - 1],
/* $FlowFixMe(>=0.13.0) - In the addition expression below this comment,
* one or both of the operands may be something that doesn't cleanly
* convert to a string, like undefined, null, and object, etc. If you really
* mean this implicit string conversion, you can do something like
* String(myThing)
*/
'inputRange must be monotonically increasing ' + arr
);
}
}
function checkInfiniteRange(name: string, arr: Array<number>) {
invariant(arr.length >= 2, name + ' must have at least 2 elements');
invariant(
arr.length !== 2 || arr[0] !== -Infinity || arr[1] !== Infinity,
/* $FlowFixMe(>=0.13.0) - In the addition expression below this comment,
* one or both of the operands may be something that doesn't cleanly convert
* to a string, like undefined, null, and object, etc. If you really mean
* this implicit string conversion, you can do something like
* String(myThing)
*/
name + 'cannot be ]-infinity;+infinity[ ' + arr
);
}
module.exports = Interpolation;

View File

@@ -0,0 +1,103 @@
/* eslint-disable */
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule SpringConfig
* @flow
*/
'use strict';
type SpringConfigType = {
tension: number,
friction: number,
};
function tensionFromOrigamiValue(oValue) {
return (oValue - 30) * 3.62 + 194;
}
function frictionFromOrigamiValue(oValue) {
return (oValue - 8) * 3 + 25;
}
function fromOrigamiTensionAndFriction(
tension: number,
friction: number,
): SpringConfigType {
return {
tension: tensionFromOrigamiValue(tension),
friction: frictionFromOrigamiValue(friction)
};
}
function fromBouncinessAndSpeed(
bounciness: number,
speed: number,
): SpringConfigType {
function normalize(value, startValue, endValue) {
return (value - startValue) / (endValue - startValue);
}
function projectNormal(n, start, end) {
return start + (n * (end - start));
}
function linearInterpolation(t, start, end) {
return t * end + (1 - t) * start;
}
function quadraticOutInterpolation(t, start, end) {
return linearInterpolation(2 * t - t * t, start, end);
}
function b3Friction1(x) {
return (0.0007 * Math.pow(x, 3)) -
(0.031 * Math.pow(x, 2)) + 0.64 * x + 1.28;
}
function b3Friction2(x) {
return (0.000044 * Math.pow(x, 3)) -
(0.006 * Math.pow(x, 2)) + 0.36 * x + 2;
}
function b3Friction3(x) {
return (0.00000045 * Math.pow(x, 3)) -
(0.000332 * Math.pow(x, 2)) + 0.1078 * x + 5.84;
}
function b3Nobounce(tension) {
if (tension <= 18) {
return b3Friction1(tension);
} else if (tension > 18 && tension <= 44) {
return b3Friction2(tension);
} else {
return b3Friction3(tension);
}
}
var b = normalize(bounciness / 1.7, 0, 20);
b = projectNormal(b, 0, 0.8);
var s = normalize(speed / 1.7, 0, 20);
var bouncyTension = projectNormal(s, 0.5, 200);
var bouncyFriction = quadraticOutInterpolation(
b,
b3Nobounce(bouncyTension),
0.01
);
return {
tension: tensionFromOrigamiValue(bouncyTension),
friction: frictionFromOrigamiValue(bouncyFriction)
};
}
module.exports = {
fromOrigamiTensionAndFriction,
fromBouncinessAndSpeed,
};

View File

@@ -0,0 +1,19 @@
/**
* Copyright (c) 2016-present, Nicolas Gallagher.
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* @flow
*/
import AnimatedImplementation from './AnimatedImplementation'
import Image from '../../components/Image'
import Text from '../../components/Text'
import View from '../../components/View'
module.exports = {
...AnimatedImplementation,
View: AnimatedImplementation.createAnimatedComponent(View),
Text: AnimatedImplementation.createAnimatedComponent(Text),
Image: AnimatedImplementation.createAnimatedComponent(Image)
}

View File

@@ -0,0 +1,15 @@
function SetPolyfill() {
this._cache = []
}
SetPolyfill.prototype.add = function (e) {
if (this._cache.indexOf(e) === -1) {
this._cache.push(e)
}
}
SetPolyfill.prototype.forEach = function (cb) {
this._cache.forEach(cb)
}
export default SetPolyfill

View File

@@ -0,0 +1,49 @@
import Portal from '../../components/Portal'
import React, { Component, PropTypes } from 'react'
import ReactDOM from 'react-dom'
import StyleSheet from '../StyleSheet'
import View from '../../components/View'
class ReactNativeApp extends Component {
static propTypes = {
initialProps: PropTypes.object,
rootComponent: PropTypes.any.isRequired,
rootTag: PropTypes.any
};
constructor(props, context) {
super(props, context)
this._handleModalVisibilityChange = this._handleModalVisibilityChange.bind(this)
}
_handleModalVisibilityChange(modalVisible) {
ReactDOM.findDOMNode(this._root).setAttribute('aria-hidden', `${modalVisible}`)
}
render() {
const { initialProps, rootComponent: RootComponent, rootTag } = this.props
return (
<View style={styles.appContainer}>
<RootComponent {...initialProps} ref={(c) => { this._root = c }} rootTag={rootTag} />
<Portal onModalVisibilityChanged={this._handleModalVisibilityChange} />
</View>
)
}
}
const styles = StyleSheet.create({
/**
* Ensure that the application covers the whole screen. This prevents the
* Portal content from being clipped.
*/
appContainer: {
position: 'absolute',
left: 0,
top: 0,
right: 0,
bottom: 0
}
})
module.exports = ReactNativeApp

View File

@@ -0,0 +1,92 @@
/**
* Copyright (c) 2015-present, Nicolas Gallagher.
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* @flow
*/
import { Component } from 'react'
import invariant from 'fbjs/lib/invariant'
import ReactDOM from 'react-dom'
import renderApplication, { prerenderApplication } from './renderApplication'
const runnables = {}
type ComponentProvider = () => Component<any, any, any>
type AppConfig = {
appKey: string;
component?: ComponentProvider;
run?: Function;
};
/**
* `AppRegistry` is the JS entry point to running all React Native apps.
*/
class AppRegistry {
static getAppKeys(): Array<string> {
return Object.keys(runnables)
}
static prerenderApplication(appKey: string, appParameters?: Object): string {
invariant(
runnables[appKey] && runnables[appKey].prerender,
`Application ${appKey} has not been registered. ` +
`This is either due to an import error during initialization or failure to call AppRegistry.registerComponent.`
)
return runnables[appKey].prerender(appParameters)
}
static registerComponent(appKey: string, getComponentFunc: ComponentProvider): string {
runnables[appKey] = {
run: ({ initialProps, rootTag }) => renderApplication(getComponentFunc(), initialProps, rootTag),
prerender: ({ initialProps } = {}) => prerenderApplication(getComponentFunc(), initialProps)
}
return appKey
}
static registerConfig(config: Array<AppConfig>) {
config.forEach(({ appKey, component, run }) => {
if (run) {
AppRegistry.registerRunnable(appKey, run)
} else {
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
}
static runApplication(appKey: string, appParameters?: Object): void {
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)
}
static unmountApplicationComponentAtRootTag(rootTag) {
ReactDOM.unmountComponentAtNode(rootTag)
}
}
module.exports = AppRegistry

View File

@@ -0,0 +1,45 @@
/**
* Copyright (c) 2015-present, Nicolas Gallagher.
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* @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'
const renderStyleSheetToString = () => `<style id="${StyleSheet.elementId}">${StyleSheet._renderToString()}</style>`
export default function renderApplication(RootComponent: Component, initialProps: Object, rootTag: any) {
invariant(rootTag, 'Expect to have a valid rootTag, instead got ', rootTag)
// insert style sheet if needed
const styleElement = document.getElementById(StyleSheet.elementId)
if (!styleElement) { rootTag.insertAdjacentHTML('beforebegin', renderStyleSheetToString()) }
const component = (
<ReactNativeApp
initialProps={initialProps}
rootComponent={RootComponent}
rootTag={rootTag}
/>
)
ReactDOM.render(component, rootTag)
}
export function prerenderApplication(RootComponent: Component, initialProps: Object): string {
const component = (
<ReactNativeApp
initialProps={initialProps}
rootComponent={RootComponent}
/>
)
const html = ReactDOMServer.renderToString(component)
const style = renderStyleSheetToString()
return { html, style }
}

View File

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

View File

@@ -0,0 +1,31 @@
import invariant from 'fbjs/lib/invariant'
const listeners = {}
const eventTypes = [ 'change' ]
class AppState {
static get currentState() {
switch (document.visibilityState) {
case 'hidden':
case 'prerender':
case 'unloaded':
return 'background'
default:
return 'active'
}
}
static addEventListener(type: string, handler: Function) {
listeners[handler] = () => handler(AppState.currentState)
invariant(eventTypes.indexOf(type) !== -1, 'Trying to subscribe to unknown event: "%s"', type)
document.addEventListener('visibilitychange', listeners[handler], false)
}
static removeEventListener(type: string, handler: Function) {
invariant(eventTypes.indexOf(type) !== -1, 'Trying to remove listener for unknown event: "%s"', type)
document.removeEventListener('visibilitychange', listeners[handler], false)
delete listeners[handler]
}
}
module.exports = AppState

View File

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

View File

@@ -0,0 +1,161 @@
/**
* Copyright (c) 2015-present, Nicolas Gallagher.
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*/
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)
}
class AsyncStorage {
/**
* Erases *all* AsyncStorage for the domain.
*/
static clear() {
return new Promise((resolve, reject) => {
try {
window.localStorage.clear()
resolve(null)
} catch (err) {
reject(err)
}
})
}
/**
* Gets *all* keys known to the app, for all callers, libraries, etc.
*/
static getAllKeys() {
return new Promise((resolve, reject) => {
try {
const numberOfKeys = window.localStorage.length
const keys = []
for (let i = 0; i < numberOfKeys; i += 1) {
const key = window.localStorage.key(i)
keys.push(key)
}
resolve(keys)
} catch (err) {
reject(err)
}
})
}
/**
* Fetches `key` value.
*/
static getItem(key: string) {
return new Promise((resolve, reject) => {
try {
const value = window.localStorage.getItem(key)
resolve(value)
} catch (err) {
reject(err)
}
})
}
/**
* Merges existing value with input value, assuming they are stringified JSON.
*/
static mergeItem(key: string, value: string) {
return new Promise((resolve, reject) => {
try {
mergeLocalStorageItem(key, value)
resolve(null)
} catch (err) {
reject(err)
}
})
}
/**
* multiGet resolves to an array of key-value pair arrays that matches the
* input format of multiSet.
*
* multiGet(['k1', 'k2']) -> [['k1', 'val1'], ['k2', 'val2']]
*/
static multiGet(keys: Array<string>) {
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)
)
}
/**
* Takes an array of key-value array pairs and merges them with existing
* values, assuming they are stringified JSON.
*
* multiMerge([['k1', 'val1'], ['k2', 'val2']])
*/
static multiMerge(keyValuePairs: Array<Array<string>>) {
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))
return Promise.all(promises).then(
() => Promise.resolve(null),
(error) => Promise.reject(error)
)
}
/**
* Takes an array of key-value array pairs.
* multiSet([['k1', 'val1'], ['k2', 'val2']])
*/
static multiSet(keyValuePairs: Array<Array<string>>) {
const promises = keyValuePairs.map((item) => AsyncStorage.setItem(item[0], item[1]))
return Promise.all(promises).then(
() => Promise.resolve(null),
(error) => Promise.reject(error)
)
}
/**
* Removes a `key`
*/
static removeItem(key: string) {
return new Promise((resolve, reject) => {
try {
window.localStorage.removeItem(key)
resolve(null)
} catch (err) {
reject(err)
}
})
}
/**
* Sets `value` for `key`.
*/
static setItem(key: string, value: string) {
return new Promise((resolve, reject) => {
try {
window.localStorage.setItem(key, value)
resolve(null)
} catch (err) {
reject(err)
}
})
}
}
module.exports = AsyncStorage

View File

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

View File

@@ -0,0 +1,33 @@
/**
* Copyright (c) 2015-present, Nicolas Gallagher.
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* @flow
*/
import invariant from 'fbjs/lib/invariant'
const dimensions = {
screen: {
fontScale: 1,
get height() { return window.screen.height },
scale: window.devicePixelRatio || 1,
get width() { return window.screen.width }
},
window: {
fontScale: 1,
get height() { return window.innerHeight },
scale: window.devicePixelRatio || 1,
get width() { return window.innerWidth }
}
}
class Dimensions {
static get(dimension: string): Object {
invariant(dimensions[dimension], 'No dimension set for key ' + dimension)
return dimensions[dimension]
}
}
module.exports = Dimensions

83
src/apis/Easing/bezier.js Normal file
View File

@@ -0,0 +1,83 @@
/* eslint-disable */
/**
* https://github.com/arian/cubic-bezier
*
* MIT License
*
* Copyright (c) 2013 Arian Stolwijk
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* @providesModule bezier
* @nolint
*/
module.exports = function(x1, y1, x2, y2, epsilon){
var curveX = function(t){
var v = 1 - t;
return 3 * v * v * t * x1 + 3 * v * t * t * x2 + t * t * t;
};
var curveY = function(t){
var v = 1 - t;
return 3 * v * v * t * y1 + 3 * v * t * t * y2 + t * t * t;
};
var derivativeCurveX = function(t){
var v = 1 - t;
return 3 * (2 * (t - 1) * t + v * v) * x1 + 3 * (-t * t * t + 2 * v * t) * x2;
};
return function(t){
var x = t, t0, t1, t2, x2, d2, i;
// First try a few iterations of Newton's method -- normally very fast.
for (t2 = x, i = 0; i < 8; i++){
x2 = curveX(t2) - x;
if (Math.abs(x2) < epsilon) { return curveY(t2); }
d2 = derivativeCurveX(t2);
if (Math.abs(d2) < 1e-6) { break; }
t2 = t2 - x2 / d2;
}
t0 = 0;
t1 = 1;
t2 = x;
if (t2 < t0) { return curveY(t0); }
if (t2 > t1) { return curveY(t1); }
// Fallback to the bisection method for reliability.
while (t0 < t1){
x2 = curveX(t2);
if (Math.abs(x2 - x) < epsilon) { return curveY(t2); }
if (x > x2) { t0 = t2; }
else { t1 = t2; }
t2 = (t1 - t0) * 0.5 + t0;
}
// Failure
return curveY(t2);
};
};

155
src/apis/Easing/index.js Normal file
View File

@@ -0,0 +1,155 @@
/* eslint-disable */
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule Easing
* @flow
*/
'use strict';
var _bezier = require('./bezier');
/**
* This class implements common easing functions. The math is pretty obscure,
* but this cool website has nice visual illustrations of what they represent:
* http://xaedes.de/dev/transitions/
*/
class Easing {
static step0(n) {
return n > 0 ? 1 : 0;
}
static step1(n) {
return n >= 1 ? 1 : 0;
}
static linear(t) {
return t;
}
static ease(t: number): number {
return ease(t);
}
static quad(t) {
return t * t;
}
static cubic(t) {
return t * t * t;
}
static poly(n) {
return (t) => Math.pow(t, n);
}
static sin(t) {
return 1 - Math.cos(t * Math.PI / 2);
}
static circle(t) {
return 1 - Math.sqrt(1 - t * t);
}
static exp(t) {
return Math.pow(2, 10 * (t - 1));
}
/**
* A simple elastic interaction, similar to a spring. Default bounciness
* is 1, which overshoots a little bit once. 0 bounciness doesn't overshoot
* at all, and bounciness of N > 1 will overshoot about N times.
*
* Wolfram Plots:
*
* http://tiny.cc/elastic_b_1 (default bounciness = 1)
* http://tiny.cc/elastic_b_3 (bounciness = 3)
*/
static elastic(bounciness: number = 1): (t: number) => number {
var p = bounciness * Math.PI;
return (t) => 1 - Math.pow(Math.cos(t * Math.PI / 2), 3) * Math.cos(t * p);
}
static back(s: number): (t: number) => number {
if (s === undefined) {
s = 1.70158;
}
return (t) => t * t * ((s + 1) * t - s);
}
static bounce(t: number): number {
if (t < 1 / 2.75) {
return 7.5625 * t * t;
}
if (t < 2 / 2.75) {
t -= 1.5 / 2.75;
return 7.5625 * t * t + 0.75;
}
if (t < 2.5 / 2.75) {
t -= 2.25 / 2.75;
return 7.5625 * t * t + 0.9375;
}
t -= 2.625 / 2.75;
return 7.5625 * t * t + 0.984375;
}
static bezier(
x1: number,
y1: number,
x2: number,
y2: number,
epsilon?: ?number,
): (t: number) => number {
if (epsilon === undefined) {
// epsilon determines the precision of the solved values
// a good approximation is:
var duration = 500; // duration of animation in milliseconds.
epsilon = (1000 / 60 / duration) / 4;
}
return _bezier(x1, y1, x2, y2, epsilon);
}
static in(
easing: (t: number) => number,
): (t: number) => number {
return easing;
}
/**
* Runs an easing function backwards.
*/
static out(
easing: (t: number) => number,
): (t: number) => number {
return (t) => 1 - easing(1 - t);
}
/**
* Makes any easing function symmetrical.
*/
static inOut(
easing: (t: number) => number,
): (t: number) => number {
return (t) => {
if (t < 0.5) {
return easing(t * 2) / 2;
}
return 1 - easing((1 - t) * 2) / 2;
};
}
}
var ease = Easing.bezier(0.42, 0, 1, 1);
module.exports = Easing;

View File

@@ -0,0 +1,48 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* @flow
*/
import keyMirror from 'fbjs/lib/keyMirror'
import invariant from 'fbjs/lib/invariant'
const InteractionManager = {
Events: keyMirror({
interactionStart: true,
interactionComplete: true
}),
/**
* Schedule a function to run after all interactions have completed.
*/
runAfterInteractions(callback: Function) {
invariant(
typeof callback === 'function',
'Must specify a function to schedule.'
)
callback()
},
/**
* Notify manager that an interaction has started.
*/
createInteractionHandle() {
return 1
},
/**
* Notify manager that an interaction has completed.
*/
clearInteractionHandle(handle) {
invariant(
!!handle,
'Must provide a handle to clear.'
)
},
addListener: () => {}
}
module.exports = InteractionManager

View File

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

79
src/apis/NetInfo/index.js Normal file
View File

@@ -0,0 +1,79 @@
/**
* Copyright (c) 2015-present, Nicolas Gallagher.
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* @flow
*/
import invariant from 'fbjs/lib/invariant'
const connection = window.navigator.connection || window.navigator.mozConnection || window.navigator.webkitConnection
const eventTypes = [ 'change' ]
/**
* Navigator online: https://developer.mozilla.org/en-US/docs/Web/API/NavigatorOnLine/onLine
* Network Connection API: https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation
*/
const NetInfo = {
addEventListener(type: string, handler: Function): { remove: () => void } {
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.')
return {
remove: () => {}
}
}
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)
},
fetch(): Promise {
return new Promise((resolve, reject) => {
try {
resolve(connection.type)
} catch (err) {
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(true), false)
window.addEventListener('offline', handler.bind(false), false)
return {
remove: () => NetInfo.isConnected.removeEventListener(type, handler)
}
},
removeEventListener(type: string, handler: Function): void {
invariant(eventTypes.indexOf(type) !== -1, 'Trying to subscribe to unknown event: "%s"', type)
window.removeEventListener('online', handler.bind(true), false)
window.removeEventListener('offline', handler.bind(false), false)
},
fetch(): Promise {
return new Promise((resolve, reject) => {
try {
resolve(window.navigator.onLine)
} catch (err) {
resolve(true)
}
})
}
}
}
module.exports = NetInfo

View File

@@ -0,0 +1,124 @@
/* eslint-disable */
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*/
"use strict";
var TouchHistoryMath = {
/**
* This code is optimized and not intended to look beautiful. This allows
* computing of touch centroids that have moved after `touchesChangedAfter`
* timeStamp. You can compute the current centroid involving all touches
* moves after `touchesChangedAfter`, or you can compute the previous
* centroid of all touches that were moved after `touchesChangedAfter`.
*
* @param {TouchHistoryMath} touchHistory Standard Responder touch track
* data.
* @param {number} touchesChangedAfter timeStamp after which moved touches
* are considered "actively moving" - not just "active".
* @param {boolean} isXAxis Consider `x` dimension vs. `y` dimension.
* @param {boolean} ofCurrent Compute current centroid for actively moving
* touches vs. previous centroid of now actively moving touches.
* @return {number} value of centroid in specified dimension.
*/
centroidDimension: function(touchHistory, touchesChangedAfter, isXAxis, ofCurrent) {
var touchBank = touchHistory.touchBank;
var total = 0;
var count = 0;
var oneTouchData = touchHistory.numberActiveTouches === 1 ?
touchHistory.touchBank[touchHistory.indexOfSingleActiveTouch] : null;
if (oneTouchData !== null) {
if (oneTouchData.touchActive && oneTouchData.currentTimeStamp > touchesChangedAfter) {
total += ofCurrent && isXAxis ? oneTouchData.currentPageX :
ofCurrent && !isXAxis ? oneTouchData.currentPageY :
!ofCurrent && isXAxis ? oneTouchData.previousPageX :
oneTouchData.previousPageY;
count = 1;
}
} else {
for (var i = 0; i < touchBank.length; i++) {
var touchTrack = touchBank[i];
if (touchTrack !== null &&
touchTrack !== undefined &&
touchTrack.touchActive &&
touchTrack.currentTimeStamp >= touchesChangedAfter) {
var toAdd; // Yuck, program temporarily in invalid state.
if (ofCurrent && isXAxis) {
toAdd = touchTrack.currentPageX;
} else if (ofCurrent && !isXAxis) {
toAdd = touchTrack.currentPageY;
} else if (!ofCurrent && isXAxis) {
toAdd = touchTrack.previousPageX;
} else {
toAdd = touchTrack.previousPageY;
}
total += toAdd;
count++;
}
}
}
return count > 0 ? total / count : TouchHistoryMath.noCentroid;
},
currentCentroidXOfTouchesChangedAfter: function(touchHistory, touchesChangedAfter) {
return TouchHistoryMath.centroidDimension(
touchHistory,
touchesChangedAfter,
true, // isXAxis
true // ofCurrent
);
},
currentCentroidYOfTouchesChangedAfter: function(touchHistory, touchesChangedAfter) {
return TouchHistoryMath.centroidDimension(
touchHistory,
touchesChangedAfter,
false, // isXAxis
true // ofCurrent
);
},
previousCentroidXOfTouchesChangedAfter: function(touchHistory, touchesChangedAfter) {
return TouchHistoryMath.centroidDimension(
touchHistory,
touchesChangedAfter,
true, // isXAxis
false // ofCurrent
);
},
previousCentroidYOfTouchesChangedAfter: function(touchHistory, touchesChangedAfter) {
return TouchHistoryMath.centroidDimension(
touchHistory,
touchesChangedAfter,
false, // isXAxis
false // ofCurrent
);
},
currentCentroidX: function(touchHistory) {
return TouchHistoryMath.centroidDimension(
touchHistory,
0, // touchesChangedAfter
true, // isXAxis
true // ofCurrent
);
},
currentCentroidY: function(touchHistory) {
return TouchHistoryMath.centroidDimension(
touchHistory,
0, // touchesChangedAfter
false, // isXAxis
true // ofCurrent
);
},
noCentroid: -1,
};
module.exports = TouchHistoryMath;

View File

@@ -0,0 +1,392 @@
/* eslint-disable */
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*/
"use strict";
import normalizeNativeEvent from './normalizeNativeEvent';
var TouchHistoryMath = require('./TouchHistoryMath');
var currentCentroidXOfTouchesChangedAfter =
TouchHistoryMath.currentCentroidXOfTouchesChangedAfter;
var currentCentroidYOfTouchesChangedAfter =
TouchHistoryMath.currentCentroidYOfTouchesChangedAfter;
var previousCentroidXOfTouchesChangedAfter =
TouchHistoryMath.previousCentroidXOfTouchesChangedAfter;
var previousCentroidYOfTouchesChangedAfter =
TouchHistoryMath.previousCentroidYOfTouchesChangedAfter;
var currentCentroidX = TouchHistoryMath.currentCentroidX;
var currentCentroidY = TouchHistoryMath.currentCentroidY;
/**
* `PanResponder` reconciles several touches into a single gesture. It makes
* single-touch gestures resilient to extra touches, and can be used to
* recognize simple multi-touch gestures.
*
* It provides a predictable wrapper of the responder handlers provided by the
* [gesture responder system](docs/gesture-responder-system.html).
* For each handler, it provides a new `gestureState` object alongside the
* native event object:
*
* ```
* onPanResponderMove: (event, gestureState) => {}
* ```
*
* A native event is a synthetic touch event with the following form:
*
* - `nativeEvent`
* + `changedTouches` - Array of all touch events that have changed since the last event
* + `identifier` - The ID of the touch
* + `locationX` - The X position of the touch, relative to the element
* + `locationY` - The Y position of the touch, relative to the element
* + `pageX` - The X position of the touch, relative to the root element
* + `pageY` - The Y position of the touch, relative to the root element
* + `target` - The node id of the element receiving the touch event
* + `timestamp` - A time identifier for the touch, useful for velocity calculation
* + `touches` - Array of all current touches on the screen
*
* A `gestureState` object has the following:
*
* - `stateID` - ID of the gestureState- persisted as long as there at least
* one touch on screen
* - `moveX` - the latest screen coordinates of the recently-moved touch
* - `moveY` - the latest screen coordinates of the recently-moved touch
* - `x0` - the screen coordinates of the responder grant
* - `y0` - the screen coordinates of the responder grant
* - `dx` - accumulated distance of the gesture since the touch started
* - `dy` - accumulated distance of the gesture since the touch started
* - `vx` - current velocity of the gesture
* - `vy` - current velocity of the gesture
* - `numberActiveTouches` - Number of touches currently on screen
*
* ### Basic Usage
*
* ```
* componentWillMount: function() {
* this._panResponder = PanResponder.create({
* // Ask to be the responder:
* onStartShouldSetPanResponder: (evt, gestureState) => true,
* onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
* onMoveShouldSetPanResponder: (evt, gestureState) => true,
* onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
*
* onPanResponderGrant: (evt, gestureState) => {
* // The guesture has started. Show visual feedback so the user knows
* // what is happening!
*
* // gestureState.{x,y}0 will be set to zero now
* },
* onPanResponderMove: (evt, gestureState) => {
* // The most recent move distance is gestureState.move{X,Y}
*
* // The accumulated gesture distance since becoming responder is
* // gestureState.d{x,y}
* },
* onPanResponderTerminationRequest: (evt, gestureState) => true,
* onPanResponderRelease: (evt, gestureState) => {
* // The user has released all touches while this view is the
* // responder. This typically means a gesture has succeeded
* },
* onPanResponderTerminate: (evt, gestureState) => {
* // Another component has become the responder, so this gesture
* // should be cancelled
* },
* onShouldBlockNativeResponder: (evt, gestureState) => {
* // Returns whether this component should block native components from becoming the JS
* // responder. Returns true by default. Is currently only supported on android.
* return true;
* },
* });
* },
*
* render: function() {
* return (
* <View {...this._panResponder.panHandlers} />
* );
* },
*
* ```
*
* ### Working Example
*
* To see it in action, try the
* [PanResponder example in UIExplorer](https://github.com/facebook/react-native/blob/master/Examples/UIExplorer/PanResponderExample.js)
*/
var PanResponder = {
/**
*
* A graphical explanation of the touch data flow:
*
* +----------------------------+ +--------------------------------+
* | ResponderTouchHistoryStore | |TouchHistoryMath |
* +----------------------------+ +----------+---------------------+
* |Global store of touchHistory| |Allocation-less math util |
* |including activeness, start | |on touch history (centroids |
* |position, prev/cur position.| |and multitouch movement etc) |
* | | | |
* +----^-----------------------+ +----^---------------------------+
* | |
* | (records relevant history |
* | of touches relevant for |
* | implementing higher level |
* | gestures) |
* | |
* +----+-----------------------+ +----|---------------------------+
* | ResponderEventPlugin | | | Your App/Component |
* +----------------------------+ +----|---------------------------+
* |Negotiates which view gets | Low level | | High level |
* |onResponderMove events. | events w/ | +-+-------+ events w/ |
* |Also records history into | touchHistory| | Pan | multitouch + |
* |ResponderTouchHistoryStore. +---------------->Responder+-----> accumulative|
* +----------------------------+ attached to | | | distance and |
* each event | +---------+ velocity. |
* | |
* | |
* +--------------------------------+
*
*
*
* Gesture that calculates cumulative movement over time in a way that just
* "does the right thing" for multiple touches. The "right thing" is very
* nuanced. When moving two touches in opposite directions, the cumulative
* distance is zero in each dimension. When two touches move in parallel five
* pixels in the same direction, the cumulative distance is five, not ten. If
* two touches start, one moves five in a direction, then stops and the other
* touch moves fives in the same direction, the cumulative distance is ten.
*
* This logic requires a kind of processing of time "clusters" of touch events
* so that two touch moves that essentially occur in parallel but move every
* other frame respectively, are considered part of the same movement.
*
* Explanation of some of the non-obvious fields:
*
* - moveX/moveY: If no move event has been observed, then `(moveX, moveY)` is
* invalid. If a move event has been observed, `(moveX, moveY)` is the
* centroid of the most recently moved "cluster" of active touches.
* (Currently all move have the same timeStamp, but later we should add some
* threshold for what is considered to be "moving"). If a palm is
* accidentally counted as a touch, but a finger is moving greatly, the palm
* will move slightly, but we only want to count the single moving touch.
* - x0/y0: Centroid location (non-cumulative) at the time of becoming
* responder.
* - dx/dy: Cumulative touch distance - not the same thing as sum of each touch
* distance. Accounts for touch moves that are clustered together in time,
* moving the same direction. Only valid when currently responder (otherwise,
* it only represents the drag distance below the threshold).
* - vx/vy: Velocity.
*/
_initializeGestureState: function(gestureState) {
gestureState.moveX = 0;
gestureState.moveY = 0;
gestureState.x0 = 0;
gestureState.y0 = 0;
gestureState.dx = 0;
gestureState.dy = 0;
gestureState.vx = 0;
gestureState.vy = 0;
gestureState.numberActiveTouches = 0;
// All `gestureState` accounts for timeStamps up until:
gestureState._accountsForMovesUpTo = 0;
},
/**
* This is nuanced and is necessary. It is incorrect to continuously take all
* active *and* recently moved touches, find the centroid, and track how that
* result changes over time. Instead, we must take all recently moved
* touches, and calculate how the centroid has changed just for those
* recently moved touches, and append that change to an accumulator. This is
* to (at least) handle the case where the user is moving three fingers, and
* then one of the fingers stops but the other two continue.
*
* This is very different than taking all of the recently moved touches and
* storing their centroid as `dx/dy`. For correctness, we must *accumulate
* changes* in the centroid of recently moved touches.
*
* There is also some nuance with how we handle multiple moved touches in a
* single event. With the way `ReactNativeEventEmitter` dispatches touches as
* individual events, multiple touches generate two 'move' events, each of
* them triggering `onResponderMove`. But with the way `PanResponder` works,
* all of the gesture inference is performed on the first dispatch, since it
* looks at all of the touches (even the ones for which there hasn't been a
* native dispatch yet). Therefore, `PanResponder` does not call
* `onResponderMove` passed the first dispatch. This diverges from the
* typical responder callback pattern (without using `PanResponder`), but
* avoids more dispatches than necessary.
*/
_updateGestureStateOnMove: function(gestureState, touchHistory) {
gestureState.numberActiveTouches = touchHistory.numberActiveTouches;
gestureState.moveX = currentCentroidXOfTouchesChangedAfter(
touchHistory,
gestureState._accountsForMovesUpTo
);
gestureState.moveY = currentCentroidYOfTouchesChangedAfter(
touchHistory,
gestureState._accountsForMovesUpTo
);
var movedAfter = gestureState._accountsForMovesUpTo;
var prevX = previousCentroidXOfTouchesChangedAfter(touchHistory, movedAfter);
var x = currentCentroidXOfTouchesChangedAfter(touchHistory, movedAfter);
var prevY = previousCentroidYOfTouchesChangedAfter(touchHistory, movedAfter);
var y = currentCentroidYOfTouchesChangedAfter(touchHistory, movedAfter);
var nextDX = gestureState.dx + (x - prevX);
var nextDY = gestureState.dy + (y - prevY);
// TODO: This must be filtered intelligently.
var dt =
(touchHistory.mostRecentTimeStamp - gestureState._accountsForMovesUpTo);
gestureState.vx = (nextDX - gestureState.dx) / dt;
gestureState.vy = (nextDY - gestureState.dy) / dt;
gestureState.dx = nextDX;
gestureState.dy = nextDY;
gestureState._accountsForMovesUpTo = touchHistory.mostRecentTimeStamp;
},
/**
* @param {object} config Enhanced versions of all of the responder callbacks
* that provide not only the typical `ResponderSyntheticEvent`, but also the
* `PanResponder` gesture state. Simply replace the word `Responder` with
* `PanResponder` in each of the typical `onResponder*` callbacks. For
* example, the `config` object would look like:
*
* - `onMoveShouldSetPanResponder: (e, gestureState) => {...}`
* - `onMoveShouldSetPanResponderCapture: (e, gestureState) => {...}`
* - `onStartShouldSetPanResponder: (e, gestureState) => {...}`
* - `onStartShouldSetPanResponderCapture: (e, gestureState) => {...}`
* - `onPanResponderReject: (e, gestureState) => {...}`
* - `onPanResponderGrant: (e, gestureState) => {...}`
* - `onPanResponderStart: (e, gestureState) => {...}`
* - `onPanResponderEnd: (e, gestureState) => {...}`
* - `onPanResponderRelease: (e, gestureState) => {...}`
* - `onPanResponderMove: (e, gestureState) => {...}`
* - `onPanResponderTerminate: (e, gestureState) => {...}`
* - `onPanResponderTerminationRequest: (e, gestureState) => {...}`
* - `onShouldBlockNativeResponder: (e, gestureState) => {...}`
*
* In general, for events that have capture equivalents, we update the
* gestureState once in the capture phase and can use it in the bubble phase
* as well.
*
* Be careful with onStartShould* callbacks. They only reflect updated
* `gestureState` for start/end events that bubble/capture to the Node.
* Once the node is the responder, you can rely on every start/end event
* being processed by the gesture and `gestureState` being updated
* accordingly. (numberActiveTouches) may not be totally accurate unless you
* are the responder.
*/
create: function(config) {
var gestureState = {
// Useful for debugging
stateID: Math.random(),
};
PanResponder._initializeGestureState(gestureState);
var panHandlers = {
onStartShouldSetResponder: function(e) {
return config.onStartShouldSetPanResponder === undefined ? false :
config.onStartShouldSetPanResponder(normalizeEvent(e), gestureState);
},
onMoveShouldSetResponder: function(e) {
return config.onMoveShouldSetPanResponder === undefined ? false :
config.onMoveShouldSetPanResponder(normalizeEvent(e), gestureState);
},
onStartShouldSetResponderCapture: function(e) {
// TODO: Actually, we should reinitialize the state any time
// touches.length increases from 0 active to > 0 active.
if (e.nativeEvent.touches) {
if (e.nativeEvent.touches.length === 1) {
PanResponder._initializeGestureState(gestureState);
}
}
else if (e.nativeEvent.type === 'mousedown') {
PanResponder._initializeGestureState(gestureState);
}
gestureState.numberActiveTouches = e.touchHistory.numberActiveTouches;
return config.onStartShouldSetPanResponderCapture !== undefined ?
config.onStartShouldSetPanResponderCapture(normalizeEvent(e), gestureState) : false;
},
onMoveShouldSetResponderCapture: function(e) {
var touchHistory = e.touchHistory;
// Responder system incorrectly dispatches should* to current responder
// Filter out any touch moves past the first one - we would have
// already processed multi-touch geometry during the first event.
if (gestureState._accountsForMovesUpTo === touchHistory.mostRecentTimeStamp) {
return false;
}
PanResponder._updateGestureStateOnMove(gestureState, touchHistory);
return config.onMoveShouldSetPanResponderCapture ?
config.onMoveShouldSetPanResponderCapture(normalizeEvent(e), gestureState) : false;
},
onResponderGrant: function(e) {
gestureState.x0 = currentCentroidX(e.touchHistory);
gestureState.y0 = currentCentroidY(e.touchHistory);
gestureState.dx = 0;
gestureState.dy = 0;
config.onPanResponderGrant && config.onPanResponderGrant(normalizeEvent(e), gestureState);
// TODO: t7467124 investigate if this can be removed
return config.onShouldBlockNativeResponder === undefined ? true :
config.onShouldBlockNativeResponder();
},
onResponderReject: function(e) {
config.onPanResponderReject && config.onPanResponderReject(normalizeEvent(e), gestureState);
},
onResponderRelease: function(e) {
config.onPanResponderRelease && config.onPanResponderRelease(normalizeEvent(e), gestureState);
PanResponder._initializeGestureState(gestureState);
},
onResponderStart: function(e) {
var touchHistory = e.touchHistory;
gestureState.numberActiveTouches = touchHistory.numberActiveTouches;
config.onPanResponderStart && config.onPanResponderStart(normalizeEvent(e), gestureState);
},
onResponderMove: function(e) {
var touchHistory = e.touchHistory;
// Guard against the dispatch of two touch moves when there are two
// simultaneously changed touches.
if (gestureState._accountsForMovesUpTo === touchHistory.mostRecentTimeStamp) {
return;
}
// Filter out any touch moves past the first one - we would have
// already processed multi-touch geometry during the first event.
PanResponder._updateGestureStateOnMove(gestureState, touchHistory);
config.onPanResponderMove && config.onPanResponderMove(normalizeEvent(e), gestureState);
},
onResponderEnd: function(e) {
var touchHistory = e.touchHistory;
gestureState.numberActiveTouches = touchHistory.numberActiveTouches;
config.onPanResponderEnd && config.onPanResponderEnd(normalizeEvent(e), gestureState);
},
onResponderTerminate: function(e) {
config.onPanResponderTerminate &&
config.onPanResponderTerminate(e, gestureState);
PanResponder._initializeGestureState(gestureState);
},
onResponderTerminationRequest: function(e) {
return config.onPanResponderTerminationRequest === undefined ? true :
config.onPanResponderTerminationRequest(normalizeEvent(e), gestureState);
},
};
return {panHandlers: panHandlers};
},
};
function normalizeEvent(e) {
const normalizedEvent = Object.create(e);
normalizedEvent.nativeEvent = normalizeNativeEvent(e.nativeEvent, e.type);
return normalizedEvent;
}
module.exports = PanResponder;

View File

@@ -0,0 +1,55 @@
// based on https://github.com/facebook/react/pull/4303/files
import EventConstants from 'react/lib/EventConstants'
import EventPluginRegistry from 'react/lib/EventPluginRegistry'
import ResponderEventPlugin from 'react/lib/ResponderEventPlugin'
import ResponderTouchHistoryStore from 'react/lib/ResponderTouchHistoryStore'
import normalizeNativeEvent from './normalizeNativeEvent'
const {
topMouseDown,
topMouseMove,
topMouseUp,
topScroll,
topSelectionChange,
topTouchCancel,
topTouchEnd,
topTouchMove,
topTouchStart
} = EventConstants.topLevelTypes
const supportsTouch = ('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch
const endDependencies = supportsTouch ? [ topTouchCancel, topTouchEnd ] : [ topMouseUp ]
const moveDependencies = supportsTouch ? [ topTouchMove ] : [ topMouseMove ]
const startDependencies = supportsTouch ? [ topTouchStart ] : [ topMouseDown ]
/**
* Setup ResponderEventPlugin dependencies
*/
ResponderEventPlugin.eventTypes.responderMove.dependencies = moveDependencies
ResponderEventPlugin.eventTypes.responderEnd.dependencies = endDependencies
ResponderEventPlugin.eventTypes.responderStart.dependencies = startDependencies
ResponderEventPlugin.eventTypes.responderRelease.dependencies = endDependencies
ResponderEventPlugin.eventTypes.responderTerminationRequest.dependencies = []
ResponderEventPlugin.eventTypes.responderGrant.dependencies = []
ResponderEventPlugin.eventTypes.responderReject.dependencies = []
ResponderEventPlugin.eventTypes.responderTerminate.dependencies = []
ResponderEventPlugin.eventTypes.moveShouldSetResponder.dependencies = moveDependencies
ResponderEventPlugin.eventTypes.selectionChangeShouldSetResponder.dependencies = [ topSelectionChange ]
ResponderEventPlugin.eventTypes.scrollShouldSetResponder.dependencies = [ topScroll ]
ResponderEventPlugin.eventTypes.startShouldSetResponder.dependencies = startDependencies
const originalRecordTouchTrack = ResponderTouchHistoryStore.recordTouchTrack
ResponderTouchHistoryStore.recordTouchTrack = (topLevelType, nativeEvent) => {
// Filter out mouse-move events when the mouse button is not down
if ((topLevelType === topMouseMove) && !ResponderTouchHistoryStore.touchHistory.touchBank.length) {
return
}
originalRecordTouchTrack.call(ResponderTouchHistoryStore, topLevelType, normalizeNativeEvent(nativeEvent))
}
EventPluginRegistry.injectEventPluginsByName({
ResponderEventPlugin
})

View File

@@ -0,0 +1,91 @@
// Mobile Safari re-uses touch objects, so we copy the properties we want and normalize the identifier
const normalizeTouches = (touches = []) => Array.prototype.slice.call(touches).map((touch) => {
const identifier = touch.identifier > 20 ? (touch.identifier % 20) : touch.identifier
const rect = touch.target && touch.target.getBoundingClientRect()
const locationX = touch.pageX - rect.left
const locationY = touch.pageY - rect.top
return {
clientX: touch.clientX,
clientY: touch.clientY,
force: touch.force,
locationX: locationX,
locationY: locationY,
identifier: identifier,
pageX: touch.pageX,
pageY: touch.pageY,
radiusX: touch.radiusX,
radiusY: touch.radiusY,
rotationAngle: touch.rotationAngle,
screenX: touch.screenX,
screenY: touch.screenY,
target: touch.target,
// normalize the timestamp
// https://stackoverflow.com/questions/26177087/ios-8-mobile-safari-wrong-timestamp-on-touch-events
timestamp: Date.now()
}
})
function normalizeTouchEvent(nativeEvent) {
const changedTouches = normalizeTouches(nativeEvent.changedTouches)
const touches = normalizeTouches(nativeEvent.touches)
const event = {
changedTouches,
domEvent: nativeEvent,
pageX: nativeEvent.pageX,
pageY: nativeEvent.pageY,
target: nativeEvent.target,
// normalize the timestamp
// https://stackoverflow.com/questions/26177087/ios-8-mobile-safari-wrong-timestamp-on-touch-events
timestamp: Date.now(),
touches
}
if (changedTouches[0]) {
event.identifier = changedTouches[0].identifier
event.pageX = changedTouches[0].pageX
event.pageY = changedTouches[0].pageY
event.locationX = changedTouches[0].locationX
event.locationY = changedTouches[0].locationY
}
return event
}
function normalizeMouseEvent(nativeEvent) {
const touches = [{
clientX: nativeEvent.clientX,
clientY: nativeEvent.clientY,
force: nativeEvent.force,
locationX: nativeEvent.clientX,
locationY: nativeEvent.clientY,
identifier: 0,
pageX: nativeEvent.pageX,
pageY: nativeEvent.pageY,
screenX: nativeEvent.screenX,
screenY: nativeEvent.screenY,
target: nativeEvent.target,
timestamp: nativeEvent.timestamp || Date.now()
}]
return {
changedTouches: touches,
domEvent: nativeEvent,
identifier: touches[0].identifier,
locationX: nativeEvent.offsetX,
locationY: nativeEvent.offsetY,
pageX: nativeEvent.pageX,
pageY: nativeEvent.pageY,
target: nativeEvent.target,
timestamp: touches[0].timestamp,
touches: (nativeEvent.type === 'mouseup') ? [] : touches
}
}
function normalizeNativeEvent(nativeEvent) {
const mouse = nativeEvent.type.indexOf('mouse') >= 0
return mouse ? normalizeMouseEvent(nativeEvent) : normalizeTouchEvent(nativeEvent)
}
module.exports = normalizeNativeEvent

View File

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

View File

@@ -0,0 +1,49 @@
/**
* Copyright (c) 2015-present, Nicolas Gallagher.
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* @flow
*/
import Dimensions from '../Dimensions'
/**
* PixelRatio gives access to the device pixel density.
*/
class PixelRatio {
/**
* Returns the device pixel density.
*/
static get(): number {
return Dimensions.get('window').scale
}
/**
* No equivalent for Web
*/
static getFontScale(): number {
return Dimensions.get('window').fontScale || PixelRatio.get()
}
/**
* Converts a layout size (dp) to pixel size (px).
* Guaranteed to return an integer number.
*/
static getPixelSizeForLayoutSize(layoutSize: number): number {
return Math.round(layoutSize * PixelRatio.get())
}
/**
* Rounds a layout size (dp) to the nearest layout size that corresponds to
* an integer number of pixels. For example, on a device with a PixelRatio
* of 3, `PixelRatio.roundToNearestPixel(8.4) = 8.33`, which corresponds to
* exactly (8.33 * 3) = 25 pixels.
*/
static roundToNearestPixel(layoutSize: number): number {
const ratio = PixelRatio.get()
return Math.round(layoutSize * ratio) / ratio
}
}
module.exports = PixelRatio

View File

@@ -0,0 +1,8 @@
import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment'
const Platform = {
OS: 'web',
userAgent: canUseDOM ? window.navigator.userAgent : ''
}
module.exports = Platform

View File

@@ -0,0 +1,25 @@
import { PropTypes } from 'react'
import ColorPropType from '../../apis/StyleSheet/ColorPropType'
const numberOrString = PropTypes.oneOfType([ PropTypes.number, PropTypes.string ])
const BorderStylePropType = PropTypes.oneOf([ 'solid', 'dotted', 'dashed' ])
const BorderPropTypes = {
borderColor: ColorPropType,
borderTopColor: ColorPropType,
borderRightColor: ColorPropType,
borderBottomColor: ColorPropType,
borderLeftColor: ColorPropType,
borderRadius: numberOrString,
borderTopLeftRadius: numberOrString,
borderTopRightRadius: numberOrString,
borderBottomLeftRadius: numberOrString,
borderBottomRightRadius: numberOrString,
borderStyle: BorderStylePropType,
borderTopStyle: BorderStylePropType,
borderRightStyle: BorderStylePropType,
borderBottomStyle: BorderStylePropType,
borderLeftStyle: BorderStylePropType
}
module.exports = BorderPropTypes

View File

@@ -0,0 +1,62 @@
/* eslint-disable */
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ColorPropType
*/
import { PropTypes } from 'react'
import ReactPropTypeLocationNames from 'react/lib/ReactPropTypeLocationNames'
var normalizeColor = require('./normalizeColor');
var colorPropType = function(isRequired, props, propName, componentName, location, propFullName) {
var color = props[propName];
if (color === undefined || color === null) {
if (isRequired) {
var locationName = ReactPropTypeLocationNames[location];
return new Error(
'Required ' + locationName + ' `' + (propFullName || propName) +
'` was not specified in `' + componentName + '`.'
);
}
return;
}
if (typeof color === 'number') {
// Developers should not use a number, but we are using the prop type
// both for user provided colors and for transformed ones. This isn't ideal
// and should be fixed but will do for now...
return;
}
if (normalizeColor(color) === null) {
var locationName = ReactPropTypeLocationNames[location];
return new Error(
'Invalid ' + locationName + ' `' + (propFullName || propName) +
'` supplied to `' + componentName + '`: ' + color + '\n' +
`Valid color formats are
- '#f0f' (#rgb)
- '#f0fc' (#rgba)
- '#ff00ff' (#rrggbb)
- '#ff00ff00' (#rrggbbaa)
- 'rgb(255, 255, 255)'
- 'rgba(255, 255, 255, 1.0)'
- 'hsl(360, 100%, 100%)'
- 'hsla(360, 100%, 100%, 1.0)'
- 'transparent'
- 'red'
- 0xff00ff00 (0xrrggbbaa)
`);
}
};
var ColorPropType = colorPropType.bind(null, false /* isRequired */);
ColorPropType.isRequired = colorPropType.bind(null, true /* isRequired */);
module.exports = ColorPropType

View File

@@ -0,0 +1,26 @@
/* eslint-disable */
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule EdgeInsetsPropType
* @flow
*/
'use strict';
var PropTypes = require('react').PropTypes;
var createStrictShapeTypeChecker = require('./createStrictShapeTypeChecker');
var EdgeInsetsPropType = createStrictShapeTypeChecker({
top: PropTypes.number,
left: PropTypes.number,
bottom: PropTypes.number,
right: PropTypes.number,
});
module.exports = EdgeInsetsPropType;

View File

@@ -0,0 +1,54 @@
import { PropTypes } from 'react'
const { number, oneOf, oneOfType, string } = PropTypes
const numberOrString = oneOfType([ number, string ])
const LayoutPropTypes = {
// box model
borderWidth: numberOrString,
borderBottomWidth: numberOrString,
borderLeftWidth: numberOrString,
borderRightWidth: numberOrString,
borderTopWidth: numberOrString,
boxSizing: string,
height: numberOrString,
margin: numberOrString,
marginBottom: numberOrString,
marginHorizontal: numberOrString,
marginLeft: numberOrString,
marginRight: numberOrString,
marginTop: numberOrString,
marginVertical: numberOrString,
maxHeight: numberOrString,
maxWidth: numberOrString,
minHeight: numberOrString,
minWidth: numberOrString,
padding: numberOrString,
paddingBottom: numberOrString,
paddingHorizontal: numberOrString,
paddingLeft: numberOrString,
paddingRight: numberOrString,
paddingTop: numberOrString,
paddingVertical: numberOrString,
width: numberOrString,
// flexbox
alignContent: oneOf([ 'center', 'flex-end', 'flex-start', 'space-around', 'space-between', 'stretch' ]),
alignItems: oneOf([ 'baseline', 'center', 'flex-end', 'flex-start', 'stretch' ]),
alignSelf: oneOf([ 'auto', 'baseline', 'center', 'flex-end', 'flex-start', 'stretch' ]),
flex: number,
flexBasis: string,
flexDirection: oneOf([ 'column', 'column-reverse', 'row', 'row-reverse' ]),
flexGrow: number,
flexShrink: number,
flexWrap: oneOf([ 'nowrap', 'wrap', 'wrap-reverse' ]),
justifyContent: oneOf([ 'center', 'flex-end', 'flex-start', 'space-around', 'space-between' ]),
order: number,
// position
bottom: numberOrString,
left: numberOrString,
position: oneOf([ 'absolute', 'fixed', 'relative', 'static' ]),
right: numberOrString,
top: numberOrString
}
module.exports = LayoutPropTypes

View File

@@ -0,0 +1,24 @@
/* eslint-disable */
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule PointPropType
* @flow
*/
'use strict';
var PropTypes = require('react').PropTypes;
var createStrictShapeTypeChecker = require('./createStrictShapeTypeChecker');
var PointPropType = createStrictShapeTypeChecker({
x: PropTypes.number,
y: PropTypes.number,
});
module.exports = PointPropType;

View File

@@ -0,0 +1,99 @@
import prefixAll from 'inline-style-prefix-all'
import hyphenate from './hyphenate'
class Store {
constructor(
initialState:Object = {},
options:Object = { obfuscateClassNames: false }
) {
this._counter = 0
this._classNames = { ...initialState.classNames }
this._declarations = { ...initialState.declarations }
this._options = options
}
get(property, value) {
const key = this._getDeclarationKey(property, value)
return this._classNames[key]
}
set(property, value) {
if (value != null) {
const values = this._getPropertyValues(property) || []
if (values.indexOf(value) === -1) {
values.push(value)
this._setClassName(property, value)
this._setPropertyValues(property, values)
}
}
}
toString() {
const obfuscate = this._options.obfuscateClassNames
// sort the properties to ensure shorthands are first in the cascade
const properties = Object.keys(this._declarations).sort()
// transform the class name to a valid CSS selector
const getCssSelector = (property, value) => {
let className = this.get(property, value)
if (!obfuscate && className) {
className = className.replace(/[(),":?.%\\$#]/g, '\\$&')
}
return className
}
// transform the declarations into CSS rules with vendor-prefixes
const buildCSSRules = (property, values) => {
return values.reduce((cssRules, value) => {
const declarations = prefixAll({ [property]: value })
const cssDeclarations = Object.keys(declarations).reduce((str, prop) => {
const value = declarations[prop]
str += `${hyphenate(prop)}:${value};`
return str
}, '')
const selector = getCssSelector(property, value)
cssRules += `\n.${selector}{${cssDeclarations}}`
return cssRules
}, '')
}
const css = properties.reduce((css, property) => {
const values = this._declarations[property]
css += buildCSSRules(property, values)
return css
}, '')
return (`/* ${this._counter} unique declarations */${css}`)
}
_getDeclarationKey(property, value) {
return `${property}:${value}`
}
_getPropertyValues(property) {
return this._declarations[property]
}
_setPropertyValues(property, values) {
this._declarations[property] = values.map(value => value)
}
_setClassName(property, value) {
const key = this._getDeclarationKey(property, value)
const exists = !!this._classNames[key]
if (!exists) {
this._counter += 1
if (this._options.obfuscateClassNames) {
this._classNames[key] = `_s_${this._counter}`
} else {
const val = `${value}`.replace(/\s/g, '-')
this._classNames[key] = `${property}:${val}`
}
}
}
}
module.exports = Store

View File

@@ -0,0 +1,22 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* @flow
*/
import createStrictShapeTypeChecker from './createStrictShapeTypeChecker'
import flattenStyle from './flattenStyle'
module.exports = function StyleSheetPropType(shape) {
const shapePropType = createStrictShapeTypeChecker(shape)
return function (props, propName, componentName, location?) {
let newProps = props
if (props[propName]) {
// Just make a dummy prop object with only the flattened style
newProps = {}
newProps[propName] = flattenStyle(props[propName])
}
return shapePropType(newProps, propName, componentName, location)
}
}

View File

@@ -0,0 +1,49 @@
/**
* Copyright (c) 2016-present, Nicolas Gallagher.
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* @flow
*/
import prefixAll from 'inline-style-prefix-all'
import flattenStyle from './flattenStyle'
import processTransform from './processTransform'
class StyleSheetRegistry {
static registerStyle(style: Object, store): number {
if (process.env.NODE_ENV !== 'production') {
Object.freeze(style)
}
const normalizedStyle = processTransform(flattenStyle(style))
Object.keys(normalizedStyle).forEach((prop) => {
// add each declaration to the store
store.set(prop, normalizedStyle[prop])
})
}
static getStyleAsNativeProps(style, store) {
let _className
let _style = {}
const classList = []
const normalizedStyle = processTransform(flattenStyle(style))
for (const prop in normalizedStyle) {
let styleClass = store.get(prop, normalizedStyle[prop])
if (styleClass) {
classList.push(styleClass)
} else {
_style[prop] = normalizedStyle[prop]
}
}
_className = classList.join(' ')
_style = prefixAll(_style)
return { className: _className, style: _style }
}
}
module.exports = StyleSheetRegistry

View File

@@ -0,0 +1,70 @@
/**
* Copyright (c) 2016-present, Nicolas Gallagher.
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
* @flow
*/
import { PropTypes } from 'react'
import ImageStylePropTypes from '../../components/Image/ImageStylePropTypes'
import TextStylePropTypes from '../../components/Text/TextStylePropTypes'
import ViewStylePropTypes from '../../components/View/ViewStylePropTypes'
import invariant from 'fbjs/lib/invariant'
class StyleSheetValidation {
static validateStyleProp(prop, style, caller) {
if (process.env.NODE_ENV !== 'production') {
if (allStylePropTypes[prop] === undefined) {
const message1 = `"${prop}" is not a valid style property.`
const message2 = '\nValid style props: ' + JSON.stringify(Object.keys(allStylePropTypes).sort(), null, ' ')
styleError(message1, style, caller, message2)
}
const error = allStylePropTypes[prop](style, prop, caller, 'prop')
if (error) {
styleError(error.message, style, caller)
}
}
}
static validateStyle(name, styles) {
if (process.env.NODE_ENV !== 'production') {
for (const prop in styles[name]) {
StyleSheetValidation.validateStyleProp(prop, styles[name], 'StyleSheet ' + name)
}
}
}
static addValidStylePropTypes(stylePropTypes) {
for (const key in stylePropTypes) {
allStylePropTypes[key] = stylePropTypes[key]
}
}
}
const styleError = (message1, style, caller, message2) => {
invariant(
false,
message1 + '\n' + (caller || '<<unknown>>') + ': ' +
JSON.stringify(style, null, ' ') + (message2 || '')
)
}
const allStylePropTypes = {}
StyleSheetValidation.addValidStylePropTypes(ImageStylePropTypes)
StyleSheetValidation.addValidStylePropTypes(TextStylePropTypes)
StyleSheetValidation.addValidStylePropTypes(ViewStylePropTypes)
StyleSheetValidation.addValidStylePropTypes({
appearance: PropTypes.string,
clear: PropTypes.string,
cursor: PropTypes.string,
display: PropTypes.string,
direction: PropTypes.string, /* @private */
float: PropTypes.oneOf([ 'left', 'none', 'right' ]),
font: PropTypes.string, /* @private */
listStyle: PropTypes.string,
verticalAlign: PropTypes.string
})
module.exports = StyleSheetValidation

View File

@@ -0,0 +1,49 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* @flow
*/
import { PropTypes } from 'react'
const ArrayOfNumberPropType = PropTypes.arrayOf(PropTypes.number)
const numberOrString = PropTypes.oneOfType([ PropTypes.number, PropTypes.string ])
const TransformMatrixPropType = function (
props : Object,
propName : string,
componentName : string
) : ?Error {
if (props.transform && props.transformMatrix) {
return new Error(
'transformMatrix and transform styles cannot be used on the same ' +
'component'
)
}
return ArrayOfNumberPropType(props, propName, componentName)
}
const TransformPropTypes = {
transform: PropTypes.arrayOf(
PropTypes.oneOfType([
PropTypes.shape({ perspective: numberOrString }),
PropTypes.shape({ rotate: numberOrString }),
PropTypes.shape({ rotateX: numberOrString }),
PropTypes.shape({ rotateY: numberOrString }),
PropTypes.shape({ rotateZ: numberOrString }),
PropTypes.shape({ scale: numberOrString }),
PropTypes.shape({ scaleX: numberOrString }),
PropTypes.shape({ scaleY: numberOrString }),
PropTypes.shape({ skewX: numberOrString }),
PropTypes.shape({ skewY: numberOrString }),
PropTypes.shape({ translateX: numberOrString }),
PropTypes.shape({ translateY: numberOrString }),
PropTypes.shape({ translateZ: numberOrString }),
PropTypes.shape({ translate3d: PropTypes.string })
])
),
transformMatrix: TransformMatrixPropType
}
module.exports = TransformPropTypes

View File

@@ -0,0 +1,116 @@
/* eslint-env mocha */
import assert from 'assert'
import Store from '../Store'
suite('apis/StyleSheet/Store', () => {
suite('the constructor', () => {
test('initialState', () => {
const initialState = { classNames: { 'textAlign:center': '__classname__' } }
const store = new Store(initialState)
assert.deepEqual(store._classNames['textAlign:center'], '__classname__')
})
})
suite('#get', () => {
test('returns a declaration-specific className', () => {
const initialState = {
classNames: {
'textAlign:center': '__expected__',
'textAlign:left': '__error__'
}
}
const store = new Store(initialState)
assert.deepEqual(store.get('textAlign', 'center'), '__expected__')
})
})
suite('#set', () => {
test('stores declarations', () => {
const store = new Store()
store.set('textAlign', 'center')
store.set('marginTop', 0)
store.set('marginTop', 1)
store.set('marginTop', 2)
assert.deepEqual(store._declarations, {
textAlign: [ 'center' ],
marginTop: [ 0, 1, 2 ]
})
})
test('human-readable classNames', () => {
const store = new Store()
store.set('textAlign', 'center')
store.set('marginTop', 0)
store.set('marginTop', 1)
store.set('marginTop', 2)
assert.deepEqual(store._classNames, {
'textAlign:center': 'textAlign:center',
'marginTop:0': 'marginTop:0',
'marginTop:1': 'marginTop:1',
'marginTop:2': 'marginTop:2'
})
})
test('obfuscated classNames', () => {
const store = new Store({}, { obfuscateClassNames: true })
store.set('textAlign', 'center')
store.set('marginTop', 0)
store.set('marginTop', 1)
store.set('marginTop', 2)
assert.deepEqual(store._classNames, {
'textAlign:center': '_s_1',
'marginTop:0': '_s_2',
'marginTop:1': '_s_3',
'marginTop:2': '_s_4'
})
})
test('replaces space characters', () => {
const store = new Store()
store.set('backgroundPosition', 'top left')
assert.equal(store.get('backgroundPosition', 'top left'), 'backgroundPosition\:top-left')
})
})
suite('#toString', () => {
test('human-readable style sheet', () => {
const store = new Store()
store.set('textAlign', 'center')
store.set('backgroundColor', 'rgba(0,0,0,0)')
store.set('color', '#fff')
store.set('fontFamily', '"Helvetica Neue", Arial, sans-serif')
store.set('marginBottom', '0px')
store.set('width', '100%')
const expected = '/* 6 unique declarations */\n' +
'.backgroundColor\\:rgba\\(0\\,0\\,0\\,0\\){background-color:rgba(0,0,0,0);}\n' +
'.color\\:\\#fff{color:#fff;}\n' +
'.fontFamily\\:\\"Helvetica-Neue\\"\\,-Arial\\,-sans-serif{font-family:"Helvetica Neue", Arial, sans-serif;}\n' +
'.marginBottom\\:0px{margin-bottom:0px;}\n' +
'.textAlign\\:center{text-align:center;}\n' +
'.width\\:100\\%{width:100%;}'
assert.equal(store.toString(), expected)
})
test('obfuscated style sheet', () => {
const store = new Store({}, { obfuscateClassNames: true })
store.set('textAlign', 'center')
store.set('marginBottom', '0px')
store.set('margin', '1px')
store.set('margin', '2px')
store.set('margin', '3px')
const expected = '/* 5 unique declarations */\n' +
'._s_3{margin:1px;}\n' +
'._s_4{margin:2px;}\n' +
'._s_5{margin:3px;}\n' +
'._s_2{margin-bottom:0px;}\n' +
'._s_1{text-align:center;}'
assert.equal(store.toString(), expected)
})
})
})

View File

@@ -0,0 +1,45 @@
/* eslint-env mocha */
import assert from 'assert'
import expandStyle from '../expandStyle'
suite('apis/StyleSheet/expandStyle', () => {
test('style resolution', () => {
const initial = {
borderTopWidth: 1,
borderWidth: 2,
marginTop: 50,
marginVertical: 25,
margin: 10
}
const expected = {
borderTopWidth: '1px',
borderLeftWidth: '2px',
borderRightWidth: '2px',
borderBottomWidth: '2px',
marginTop: '50px',
marginBottom: '25px',
marginLeft: '10px',
marginRight: '10px'
}
assert.deepEqual(expandStyle(initial), expected)
})
test('flex', () => {
const value = 10
const initial = {
flex: value
}
const expected = {
flexGrow: value,
flexShrink: 1,
flexBasis: 'auto'
}
assert.deepEqual(expandStyle(initial), expected)
})
})

View File

@@ -0,0 +1,51 @@
/* eslint-env mocha */
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
import assert from 'assert'
import flattenStyle from '../flattenStyle'
suite('apis/StyleSheet/flattenStyle', () => {
test('should merge style objects', () => {
const style1 = {opacity: 1}
const style2 = {order: 2}
const flatStyle = flattenStyle([style1, style2])
assert.equal(flatStyle.opacity, 1)
assert.equal(flatStyle.order, 2)
})
test('should override style properties', () => {
const style1 = {backgroundColor: '#000', order: 1}
const style2 = {backgroundColor: '#023c69', order: null}
const flatStyle = flattenStyle([style1, style2])
assert.equal(flatStyle.backgroundColor, '#023c69')
assert.strictEqual(flatStyle.order, null)
})
test('should overwrite properties with `undefined`', () => {
const style1 = {backgroundColor: '#000'}
const style2 = {backgroundColor: undefined}
const flatStyle = flattenStyle([style1, style2])
assert.strictEqual(flatStyle.backgroundColor, undefined)
})
test('should not fail on falsy values', () => {
assert.doesNotThrow(() => flattenStyle([null, false, undefined]))
})
test('should recursively flatten arrays', () => {
const style1 = {order: 2}
const style2 = {opacity: 1}
const style3 = {order: 3}
const flatStyle = flattenStyle([null, [], [style1, style2], style3])
assert.equal(flatStyle.order, 3)
assert.equal(flatStyle.opacity, 1)
})
})

View File

@@ -0,0 +1,16 @@
/* eslint-env mocha */
import assert from 'assert'
import hyphenate from '../hyphenate'
suite('apis/StyleSheet/hyphenate', () => {
test('style property', () => {
assert.equal(hyphenate('alignItems'), 'align-items')
assert.equal(hyphenate('color'), 'color')
})
test('vendor prefixed style property', () => {
assert.equal(hyphenate('MozTransition'), '-moz-transition')
assert.equal(hyphenate('msTransition'), '-ms-transition')
assert.equal(hyphenate('WebkitTransition'), '-webkit-transition')
})
})

View File

@@ -0,0 +1,66 @@
/* eslint-env mocha */
import { resetCSS, predefinedCSS } from '../predefs'
import assert from 'assert'
import StyleSheet from '..'
const styles = { root: { borderWidth: 1 } }
suite('apis/StyleSheet', () => {
setup(() => {
StyleSheet._destroy()
})
suite('create', () => {
const div = document.createElement('div')
setup(() => {
document.body.appendChild(div)
StyleSheet.create(styles)
div.innerHTML = `<style id='${StyleSheet.elementId}'>${StyleSheet._renderToString()}</style>`
})
teardown(() => {
document.body.removeChild(div)
})
test('returns styles object', () => {
assert.equal(StyleSheet.create(styles), styles)
})
test('updates already-rendered style sheet', () => {
StyleSheet.create({ root: { color: 'red' } })
assert.equal(
document.getElementById(StyleSheet.elementId).textContent,
`${resetCSS}\n${predefinedCSS}\n` +
`/* 5 unique declarations */\n` +
`.borderBottomWidth\\:1px{border-bottom-width:1px;}\n` +
`.borderLeftWidth\\:1px{border-left-width:1px;}\n` +
`.borderRightWidth\\:1px{border-right-width:1px;}\n` +
`.borderTopWidth\\:1px{border-top-width:1px;}\n` +
`.color\\:red{color:red;}`
)
})
})
test('resolve', () => {
const props = { style: styles.root }
const expected = { className: 'borderTopWidth:1px borderRightWidth:1px borderBottomWidth:1px borderLeftWidth:1px', style: {} }
StyleSheet.create(styles)
assert.deepEqual(StyleSheet.resolve(props), expected)
})
test('_renderToString', () => {
StyleSheet.create(styles)
assert.equal(
StyleSheet._renderToString(),
`${resetCSS}\n${predefinedCSS}\n` +
`/* 4 unique declarations */\n` +
`.borderBottomWidth\\:1px{border-bottom-width:1px;}\n` +
`.borderLeftWidth\\:1px{border-left-width:1px;}\n` +
`.borderRightWidth\\:1px{border-right-width:1px;}\n` +
`.borderTopWidth\\:1px{border-top-width:1px;}`
)
})
})

View File

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

View File

@@ -0,0 +1,70 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
*/
import invariant from 'fbjs/lib/invariant'
import ReactPropTypeLocationNames from 'react/lib/ReactPropTypeLocationNames'
module.exports = function createStrictShapeTypeChecker(shapeTypes) {
function checkType(isRequired, props, propName, componentName, location?) {
if (!props[propName]) {
if (isRequired) {
invariant(
false,
`Required object \`${propName}\` was not specified in ` +
`\`${componentName}\`.`
)
}
return
}
const propValue = props[propName]
const propType = typeof propValue
const locationName = location && ReactPropTypeLocationNames[location] || '(unknown)'
if (propType !== 'object') {
invariant(
false,
`Invalid ${locationName} \`${propName}\` of type \`${propType}\` ` +
`supplied to \`${componentName}\`, expected \`object\`.`
)
}
// We need to check all keys in case some are required but missing from
// props.
const allKeys = { ...props[propName], ...shapeTypes }
for (const key in allKeys) {
const checker = shapeTypes[key]
if (!checker) {
invariant(
false,
`Invalid props.${propName} key \`${key}\` supplied to \`${componentName}\`.` +
`\nBad object: ` + JSON.stringify(props[propName], null, ' ') +
`\nValid keys: ` + JSON.stringify(Object.keys(shapeTypes), null, ' ')
)
}
const error = checker(propValue, key, componentName, location)
if (error) {
invariant(
false,
error.message + `\nBad object: ` + JSON.stringify(props[propName], null, ' ')
)
}
}
}
function chainedCheckType(
props: {[key: string]: any},
propName: string,
componentName: string,
location?: string
): ?Error {
return checkType(false, props, propName, componentName, location)
}
chainedCheckType.isRequired = checkType.bind(null, true)
return chainedCheckType
}

View File

@@ -0,0 +1,59 @@
import normalizeValue from './normalizeValue'
const styleShortHands = {
borderColor: [ 'borderTopColor', 'borderRightColor', 'borderBottomColor', 'borderLeftColor' ],
borderRadius: [ 'borderTopLeftRadius', 'borderTopRightRadius', 'borderBottomRightRadius', 'borderBottomLeftRadius' ],
borderStyle: [ 'borderTopStyle', 'borderRightStyle', 'borderBottomStyle', 'borderLeftStyle' ],
borderWidth: [ 'borderTopWidth', 'borderRightWidth', 'borderBottomWidth', 'borderLeftWidth' ],
margin: [ 'marginTop', 'marginRight', 'marginBottom', 'marginLeft' ],
marginHorizontal: [ 'marginRight', 'marginLeft' ],
marginVertical: [ 'marginTop', 'marginBottom' ],
padding: [ 'paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft' ],
paddingHorizontal: [ 'paddingRight', 'paddingLeft' ],
paddingVertical: [ 'paddingTop', 'paddingBottom' ],
writingDirection: [ 'direction' ]
}
/**
* Alpha-sort properties, apart from shorthands which appear before the
* properties they expand into. This ensures that more specific styles override
* the shorthands, whatever the order in which they were originally declared.
*/
const sortProps = (propsArray) => propsArray.sort((a, b) => {
const expandedA = styleShortHands[a]
const expandedB = styleShortHands[b]
if (expandedA && expandedA.indexOf(b) > -1) {
return -1
} else if (expandedB && expandedB.indexOf(a) > -1) {
return 1
}
return a < b ? -1 : a > b ? 1 : 0
})
/**
* Expand the shorthand properties to isolate every declaration from the others.
*/
const expandStyle = (style) => {
const propsArray = Object.keys(style)
const sortedProps = sortProps(propsArray)
return sortedProps.reduce((resolvedStyle, key) => {
const expandedProps = styleShortHands[key]
const value = normalizeValue(key, style[key])
if (expandedProps) {
expandedProps.forEach((prop, i) => {
resolvedStyle[expandedProps[i]] = value
})
} else if (key === 'flex') {
resolvedStyle.flexGrow = value
resolvedStyle.flexShrink = 1
resolvedStyle.flexBasis = 'auto'
} else {
resolvedStyle[key] = value
}
return resolvedStyle
}, {})
}
module.exports = expandStyle

View File

@@ -0,0 +1,34 @@
/**
* Copyright (c) 2016-present, Nicolas Gallagher.
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* @flow
*/
import invariant from 'fbjs/lib/invariant'
import expandStyle from './expandStyle'
module.exports = function flattenStyle(style): ?Object {
if (!style) {
return undefined
}
invariant(style !== true, 'style may be false but not true')
if (!Array.isArray(style)) {
// we must expand styles during the flattening because expanded styles
// override shorthands
return expandStyle(style)
}
const result = {}
for (let i = 0; i < style.length; ++i) {
const computedStyle = flattenStyle(style[i])
if (computedStyle) {
for (const key in computedStyle) {
result[key] = computedStyle[key]
}
}
}
return result
}

View File

@@ -0,0 +1 @@
module.exports = (string) => (string.replace(/([A-Z])/g, '-$1').toLowerCase()).replace(/^ms-/, '-ms-')

View File

@@ -0,0 +1,75 @@
import { resetCSS, predefinedCSS, predefinedClassNames } from './predefs'
import flattenStyle from './flattenStyle'
import Store from './Store'
import StyleSheetRegistry from './StyleSheetRegistry'
import StyleSheetValidation from './StyleSheetValidation'
const ELEMENT_ID = 'react-stylesheet'
let isRendered = false
let lastStyleSheet = ''
/**
* Initialize the store with pointer-event styles mapping to our custom pointer
* event classes
*/
const initialState = { classNames: predefinedClassNames }
const options = { obfuscateClassNames: !(process.env.NODE_ENV !== 'production') }
const createStore = () => new Store(initialState, options)
let store = createStore()
/**
* Destroy existing styles
*/
const _destroy = () => {
store = createStore()
isRendered = false
}
/**
* Render the styles as a CSS style sheet
*/
const _renderToString = () => {
const css = store.toString()
isRendered = true
return `${resetCSS}\n${predefinedCSS}\n${css}`
}
const create = (styles: Object): Object => {
for (const key in styles) {
StyleSheetValidation.validateStyle(key, styles)
StyleSheetRegistry.registerStyle(styles[key], store)
}
// update the style sheet in place
if (isRendered) {
const stylesheet = document.getElementById(ELEMENT_ID)
if (stylesheet) {
const newStyleSheet = _renderToString()
if (lastStyleSheet !== newStyleSheet) {
stylesheet.textContent = newStyleSheet
lastStyleSheet = newStyleSheet
}
} else if (process.env.NODE_ENV !== 'production') {
console.error('ReactNative: cannot find "react-stylesheet" element')
}
}
return styles
}
/**
* Accepts React props and converts inline styles to single purpose classes
* where possible.
*/
const resolve = ({ style = {} }) => {
return StyleSheetRegistry.getStyleAsNativeProps(style, store)
}
module.exports = {
_destroy,
_renderToString,
create,
elementId: ELEMENT_ID,
flatten: flattenStyle,
resolve
}

View File

@@ -0,0 +1,352 @@
/* eslint-disable */
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule normalizeColor
* @flow
*/
/* eslint no-bitwise: 0 */
'use strict';
function normalizeColor(color: string | number): ?number {
var match;
if (typeof color === 'number') {
if (color >>> 0 === color && color >= 0 && color <= 0xffffffff) {
return color;
}
return null;
}
// Ordered based on occurrences on Facebook codebase
if ((match = matchers.hex6.exec(color))) {
return parseInt(match[1] + 'ff', 16) >>> 0;
}
if (names.hasOwnProperty(color)) {
return names[color];
}
if ((match = matchers.rgb.exec(color))) {
return (
parse255(match[1]) << 24 | // r
parse255(match[2]) << 16 | // g
parse255(match[3]) << 8 | // b
0x000000ff // a
) >>> 0;
}
if ((match = matchers.rgba.exec(color))) {
return (
parse255(match[1]) << 24 | // r
parse255(match[2]) << 16 | // g
parse255(match[3]) << 8 | // b
parse1(match[4]) // a
) >>> 0;
}
if ((match = matchers.hex3.exec(color))) {
return parseInt(
match[1] + match[1] + // r
match[2] + match[2] + // g
match[3] + match[3] + // b
'ff', // a
16
) >>> 0;
}
// https://drafts.csswg.org/css-color-4/#hex-notation
if ((match = matchers.hex8.exec(color))) {
return parseInt(match[1], 16) >>> 0;
}
if ((match = matchers.hex4.exec(color))) {
return parseInt(
match[1] + match[1] + // r
match[2] + match[2] + // g
match[3] + match[3] + // b
match[4] + match[4], // a
16
) >>> 0;
}
if ((match = matchers.hsl.exec(color))) {
return (
hslToRgb(
parse360(match[1]), // h
parsePercentage(match[2]), // s
parsePercentage(match[3]) // l
) |
0x000000ff // a
) >>> 0;
}
if ((match = matchers.hsla.exec(color))) {
return (
hslToRgb(
parse360(match[1]), // h
parsePercentage(match[2]), // s
parsePercentage(match[3]) // l
) |
parse1(match[4]) // a
) >>> 0;
}
return null;
}
function hue2rgb(p: number, q: number, t: number): number {
if (t < 0) {
t += 1;
}
if (t > 1) {
t -= 1;
}
if (t < 1 / 6) {
return p + (q - p) * 6 * t;
}
if (t < 1 / 2) {
return q;
}
if (t < 2 / 3) {
return p + (q - p) * (2 / 3 - t) * 6;
}
return p;
}
function hslToRgb(h: number, s: number, l: number): number {
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
var p = 2 * l - q;
var r = hue2rgb(p, q, h + 1 / 3);
var g = hue2rgb(p, q, h);
var b = hue2rgb(p, q, h - 1 / 3);
return (
Math.round(r * 255) << 24 |
Math.round(g * 255) << 16 |
Math.round(b * 255) << 8
);
}
// var INTEGER = '[-+]?\\d+';
var NUMBER = '[-+]?\\d*\\.?\\d+';
var PERCENTAGE = NUMBER + '%';
function call(...args) {
return '\\(\\s*(' + args.join(')\\s*,\\s*(') + ')\\s*\\)';
}
var matchers = {
rgb: new RegExp('rgb' + call(NUMBER, NUMBER, NUMBER)),
rgba: new RegExp('rgba' + call(NUMBER, NUMBER, NUMBER, NUMBER)),
hsl: new RegExp('hsl' + call(NUMBER, PERCENTAGE, PERCENTAGE)),
hsla: new RegExp('hsla' + call(NUMBER, PERCENTAGE, PERCENTAGE, NUMBER)),
hex3: /^#([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
hex4: /^#([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
hex6: /^#([0-9a-fA-F]{6})$/,
hex8: /^#([0-9a-fA-F]{8})$/,
};
function parse255(str: string): number {
var int = parseInt(str, 10);
if (int < 0) {
return 0;
}
if (int > 255) {
return 255;
}
return int;
}
function parse360(str: string): number {
var int = parseFloat(str);
return (((int % 360) + 360) % 360) / 360;
}
function parse1(str: string): number {
var num = parseFloat(str);
if (num < 0) {
return 0;
}
if (num > 1) {
return 255;
}
return Math.round(num * 255);
}
function parsePercentage(str: string): number {
// parseFloat conveniently ignores the final %
var int = parseFloat(str, 10);
if (int < 0) {
return 0;
}
if (int > 100) {
return 1;
}
return int / 100;
}
var names = {
/* @edit start */
inherit: 'inherit',
/* @edit end */
transparent: 0x00000000,
// http://www.w3.org/TR/css3-color/#svg-color
aliceblue: 0xf0f8ffff,
antiquewhite: 0xfaebd7ff,
aqua: 0x00ffffff,
aquamarine: 0x7fffd4ff,
azure: 0xf0ffffff,
beige: 0xf5f5dcff,
bisque: 0xffe4c4ff,
black: 0x000000ff,
blanchedalmond: 0xffebcdff,
blue: 0x0000ffff,
blueviolet: 0x8a2be2ff,
brown: 0xa52a2aff,
burlywood: 0xdeb887ff,
burntsienna: 0xea7e5dff,
cadetblue: 0x5f9ea0ff,
chartreuse: 0x7fff00ff,
chocolate: 0xd2691eff,
coral: 0xff7f50ff,
cornflowerblue: 0x6495edff,
cornsilk: 0xfff8dcff,
crimson: 0xdc143cff,
cyan: 0x00ffffff,
darkblue: 0x00008bff,
darkcyan: 0x008b8bff,
darkgoldenrod: 0xb8860bff,
darkgray: 0xa9a9a9ff,
darkgreen: 0x006400ff,
darkgrey: 0xa9a9a9ff,
darkkhaki: 0xbdb76bff,
darkmagenta: 0x8b008bff,
darkolivegreen: 0x556b2fff,
darkorange: 0xff8c00ff,
darkorchid: 0x9932ccff,
darkred: 0x8b0000ff,
darksalmon: 0xe9967aff,
darkseagreen: 0x8fbc8fff,
darkslateblue: 0x483d8bff,
darkslategray: 0x2f4f4fff,
darkslategrey: 0x2f4f4fff,
darkturquoise: 0x00ced1ff,
darkviolet: 0x9400d3ff,
deeppink: 0xff1493ff,
deepskyblue: 0x00bfffff,
dimgray: 0x696969ff,
dimgrey: 0x696969ff,
dodgerblue: 0x1e90ffff,
firebrick: 0xb22222ff,
floralwhite: 0xfffaf0ff,
forestgreen: 0x228b22ff,
fuchsia: 0xff00ffff,
gainsboro: 0xdcdcdcff,
ghostwhite: 0xf8f8ffff,
gold: 0xffd700ff,
goldenrod: 0xdaa520ff,
gray: 0x808080ff,
green: 0x008000ff,
greenyellow: 0xadff2fff,
grey: 0x808080ff,
honeydew: 0xf0fff0ff,
hotpink: 0xff69b4ff,
indianred: 0xcd5c5cff,
indigo: 0x4b0082ff,
ivory: 0xfffff0ff,
khaki: 0xf0e68cff,
lavender: 0xe6e6faff,
lavenderblush: 0xfff0f5ff,
lawngreen: 0x7cfc00ff,
lemonchiffon: 0xfffacdff,
lightblue: 0xadd8e6ff,
lightcoral: 0xf08080ff,
lightcyan: 0xe0ffffff,
lightgoldenrodyellow: 0xfafad2ff,
lightgray: 0xd3d3d3ff,
lightgreen: 0x90ee90ff,
lightgrey: 0xd3d3d3ff,
lightpink: 0xffb6c1ff,
lightsalmon: 0xffa07aff,
lightseagreen: 0x20b2aaff,
lightskyblue: 0x87cefaff,
lightslategray: 0x778899ff,
lightslategrey: 0x778899ff,
lightsteelblue: 0xb0c4deff,
lightyellow: 0xffffe0ff,
lime: 0x00ff00ff,
limegreen: 0x32cd32ff,
linen: 0xfaf0e6ff,
magenta: 0xff00ffff,
maroon: 0x800000ff,
mediumaquamarine: 0x66cdaaff,
mediumblue: 0x0000cdff,
mediumorchid: 0xba55d3ff,
mediumpurple: 0x9370dbff,
mediumseagreen: 0x3cb371ff,
mediumslateblue: 0x7b68eeff,
mediumspringgreen: 0x00fa9aff,
mediumturquoise: 0x48d1ccff,
mediumvioletred: 0xc71585ff,
midnightblue: 0x191970ff,
mintcream: 0xf5fffaff,
mistyrose: 0xffe4e1ff,
moccasin: 0xffe4b5ff,
navajowhite: 0xffdeadff,
navy: 0x000080ff,
oldlace: 0xfdf5e6ff,
olive: 0x808000ff,
olivedrab: 0x6b8e23ff,
orange: 0xffa500ff,
orangered: 0xff4500ff,
orchid: 0xda70d6ff,
palegoldenrod: 0xeee8aaff,
palegreen: 0x98fb98ff,
paleturquoise: 0xafeeeeff,
palevioletred: 0xdb7093ff,
papayawhip: 0xffefd5ff,
peachpuff: 0xffdab9ff,
peru: 0xcd853fff,
pink: 0xffc0cbff,
plum: 0xdda0ddff,
powderblue: 0xb0e0e6ff,
purple: 0x800080ff,
rebeccapurple: 0x663399ff,
red: 0xff0000ff,
rosybrown: 0xbc8f8fff,
royalblue: 0x4169e1ff,
saddlebrown: 0x8b4513ff,
salmon: 0xfa8072ff,
sandybrown: 0xf4a460ff,
seagreen: 0x2e8b57ff,
seashell: 0xfff5eeff,
sienna: 0xa0522dff,
silver: 0xc0c0c0ff,
skyblue: 0x87ceebff,
slateblue: 0x6a5acdff,
slategray: 0x708090ff,
slategrey: 0x708090ff,
snow: 0xfffafaff,
springgreen: 0x00ff7fff,
steelblue: 0x4682b4ff,
tan: 0xd2b48cff,
teal: 0x008080ff,
thistle: 0xd8bfd8ff,
tomato: 0xff6347ff,
turquoise: 0x40e0d0ff,
violet: 0xee82eeff,
wheat: 0xf5deb3ff,
white: 0xffffffff,
whitesmoke: 0xf5f5f5ff,
yellow: 0xffff00ff,
yellowgreen: 0x9acd32ff,
};
module.exports = normalizeColor;

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