Compare commits

..

105 Commits

Author SHA1 Message Date
Nicolas Gallagher
7a16d5711c 0.0.20 2016-03-20 12:19:40 -07:00
Nicolas Gallagher
9dde70fff5 Update documentation 2016-03-20 12:19:29 -07:00
Nicolas Gallagher
203980ab66 [fix] fbjs version compatible with React Native
React Native 0.21 currently uses fbjs@0.6.x, and React Native 0.22 will
use fbjs@0.7.x.

Fix #103
2016-03-20 12:11:31 -07:00
Nicolas Gallagher
924dc36d4a [fix] refactor StyleSheet
**Problem**

StyleSheet's implementation was overly complex. It required
`flattenStyle` to use `expandStyle`, and couldn't support mapping React
Native style props to CSS properties without also exposing those CSS
properties in the API.

**Response**

- `flattenStyle` is concerned only with flattening style objects.

- `StyleSheetRegistry` is responsible for registering styles, mapping
  the React Native style prop to DOM props, and generating the CSS for
  the backing style element.

- `StyleSheetRegistry` uses a simpler approach to caching styles and
  generating style sheet strings. It also drops the unobfuscated class
  names from development mode, as the React Dev Tools can provide a
  better debugging experience (pending a fix to allow props/styles to be
  changed from the dev tools).

- `StyleSheet` will fall back to inline styles if it doesn't think a
  style sheet has been rendered into the document. The relationship is
  currently only implicit. This should be revisited.

- `StyleSheet` exports `renderToString` as part of the documented API.

- Fix processing of `transformMatrix` and add tests for
  `processTransform`.

- Fix `input[type=search]` rendering in Safari by using `display:none`
  on its pseudo-elements.

- Add support for `textDecorationLine` and `textAlignVertical`.

- Note the `View` hack to conditionally apply the `flex-shrink:0` reset
  from css-layout. This is required because React Native's approach to
  resolving `style` is to give precendence to long-hand styles
  (e.g., `flexShrink`) over short-hand styles (e.g., `flex`). This means
  the `View` reset overrides any `flex:1` declaration. To get around
  this, `flexShrink` is only set in `View` if `flex` is not set.
2016-03-20 12:09:04 -07:00
Nicolas Gallagher
9b2421cdfa [fix] Server-side rendering
`AppRegistry.prerenderApplication` now returns a style element for use
in app shells.

Guard use of `window` in APIs and Event plugin.

Fix #107
Fix #108
2016-03-20 11:43:13 -07:00
Nicolas Gallagher
36ea662402 0.0.19 2016-03-16 10:19:17 -07:00
Nicolas Gallagher
69962ae815 [fix] StyleSheet: add hairlineWidth
Fix #99
2016-03-16 10:03:37 -07:00
Nicolas Gallagher
62d1a0f83d Add Game2048 example 2016-03-16 00:55:04 -07:00
Nicolas Gallagher
910286303a Add TicTacToe example 2016-03-16 00:48:41 -07:00
Nicolas Gallagher
706fa887e6 [fix] remove invariant error from Portal 2016-03-16 00:48:41 -07:00
Nicolas Gallagher
c589d79035 Reorganize karma and webpack configs 2016-03-16 00:48:37 -07:00
Nicolas Gallagher
83e4c68461 [fix] TouchableHighlight inactive state
TouchableHighlight didn't preserve its original 'backgroundColor' after
pressOut. This was caused by the inactive background style (transparent)
being applied as an inline style, and so not merging with the original
prop style. The patch sets inactive 'backgroundColor' to 'null' so as to
remove the inline style and render the backgroundColor from props.

Fix #98
2016-03-16 00:36:06 -07:00
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
165 changed files with 11835 additions and 2347 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,6 +1,10 @@
language: node_js
node_js:
- "4.1"
- "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 run build` build the library
* `npm run examples` start the dev server and develop against live examples
* `npm run lint` run the linter
* `npm run test` run the linter and unit tests
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!

44
LICENSE
View File

@@ -1,21 +1,31 @@
The MIT License (MIT)
BSD License
Copyright (c) 2015 Nicolas Gallagher
For React Native software
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:
Copyright (c) 2015-present, Nicolas Gallagher. All rights reserved.
Copyright (c) 2015-present, Facebook, Inc. All rights reserved.
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
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.
* 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.

284
README.md
View File

@@ -2,243 +2,127 @@
[![Build Status][travis-image]][travis-url]
[![npm version][npm-image]][npm-url]
![gzipped size](https://img.shields.io/badge/gzipped-~18.6k-blue.svg)
![gzipped size](https://img.shields.io/badge/gzipped-~41.0k-blue.svg)
[React Native][react-native-url] components and APIs for the Web.
* [Discord: #react-native-web on reactiflux][discord-url]
* [Gitter: react-native-web][gitter-url]
Browser support: Chrome, Firefox, Safari >= 7, IE 10, Edge.
## Table of contents
## Overview
* [Install](#install)
* [Example](#example)
* [APIs](#apis)
* [Components](#components)
* [Styling](#styling)
* [Accessibility](#accessibility)
* [Contributing](#contributing)
* [Thanks](#thanks)
* [License](#license)
"React Native for Web" is a project to bring React Native's building blocks and
touch handling to the Web.
## Install
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-dom react-native-web
npm install --save react@0.14 react-dom@0.14 react-native-web
```
## Example
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 defined with, and used as JavaScript objects.
## Examples
Component:
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, { Image, StyleSheet, Text, View } from 'react-native-web'
import React, { AppRegistry, Image, StyleSheet, Text, View } from 'react-native'
// Components
const Card = ({ children }) => <View style={styles.card}>{children}</View>
const Title = ({ children }) => <Text style={styles.title}>{children}</Text>
const Summary = ({ children }) => (
<View style={styles.text}>
<Text style={styles.subtitle}>{children}</Text>
</View>
const Photo = ({ uri }) => <Image source={{ uri }} style={styles.image} />
const App = () => (
<Card>
<Title>App Card</Title>
<Photo uri="/some-photo.jpg" />
</Card>
)
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}
/>
<Title>React Native Web</Title>
<Summary>Build high quality web apps using React</Summary>
</View>
)
},
})
// Styles
const styles = StyleSheet.create({
row: {
flexDirection: 'row',
margin: 40
},
image: {
height: 40,
marginRight: 10,
width: 40,
},
text: {
flex: 1,
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') })
```
Pre-render styles on the server:
## Documentation
```js
// server.js
import App from './components/App'
import React, { StyleSheet } from 'react-native-web'
Guides:
const html = React.renderToString(<App />);
const css = StyleSheet.renderToString();
* [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)
const Html = () => (
<html>
<head>
<meta charSet="utf-8" />
<meta content="initial-scale=1,width=device-width" name="viewport" />
<style id="react-stylesheet" dangerouslySetInnerHTML={{ __html: css } />
</head>
<body>
<div id="react-root" dangerouslySetInnerHTML={{ __html: html }} />
</body>
</html>
)
```
Exported modules:
Render styles on the client:
```js
// client.js
import App from './components/App'
import React, { StyleSheet } from 'react-native-web'
import ReactDOM from 'react-dom'
const reactRoot = document.getElementById('react-root')
const reactStyleSheet = document.getElementById('react-stylesheet')
ReactDOM.render(<App />, reactRoot)
reactStyleSheet.textContent = StyleSheet.renderToString()
```
## APIs
### [`StyleSheet`](docs/apis/StyleSheet.md)
StyleSheet is a style abstraction that transforms inline styles to CSS on the
client or the server. It provides a minimal CSS reset.
## 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/ScrollView.md)
A scrollable view with event throttling.
### [`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.
### [`Touchable`](docs/components/Touchable.md)
Touch bindings for press and long press.
### [`View`](docs/components/View.md)
The fundamental UI building block using flexbox for layout.
## Styling
React Native for Web relies on styles being defined in JavaScript. Styling
components can be achieved with inline styles or the use of
[StyleSheet](docs/apis/StyleSheet.md).
The `View` component makes it easy to build common layouts with flexbox, such
as stacked and nested boxes with margin and padding. See this [guide to
flexbox][flexbox-guide-url].
### Media Queries, pseudo-classes, and pseudo-elements
Changing styles and/or the render tree in response to device adaptation can be
controlled in JavaScript, 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 like `:hover` and `:focus` can be implemented with the `onHover`
and `onFocus` events.
Pseudo-elements are not supported.
## 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 rendered
HTML element. The `accessibilityRole` prop is used to infer an [analogous HTML
element][html-aria-url] to use in addition, 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' />`.
See the component documentation for more details.
## Contributing
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).
Thanks 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).
[aria-in-html-url]: https://w3c.github.io/aria-in-html/
[contributing-url]: https://github.com/necolas/react-native-web/blob/master/CONTRIBUTING.md
[discord-url]: http://join.reactiflux.com
[flexbox-guide-url]: https://css-tricks.com/snippets/css/a-guide-to-flexbox/
[gitter-url]: https://gitter.im/necolas/react-native-web
[html-accessibility-url]: http://www.html5accessibility.com/
[html-aria-url]: http://www.w3.org/TR/html-aria/
[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/

View File

@@ -1,11 +0,0 @@
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,39 +0,0 @@
var webpack = require('webpack')
var DedupePlugin = webpack.optimize.DedupePlugin
var EnvironmentPlugin = webpack.EnvironmentPlugin
var OccurenceOrderPlugin = webpack.optimize.OccurenceOrderPlugin
var UglifyJsPlugin = webpack.optimize.UglifyJsPlugin
var plugins = [
new DedupePlugin(),
new EnvironmentPlugin('NODE_ENV'),
new OccurenceOrderPlugin()
]
if (process.env.NODE_ENV === 'production') {
plugins.push(
new UglifyJsPlugin({
compress: {
dead_code: true,
drop_console: true,
screw_ie8: true,
warnings: true
}
})
)
}
module.exports = {
module: {
loaders: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: 'babel-loader',
query: { cacheDirectory: true }
}
]
},
plugins: plugins
}

View File

@@ -1,17 +0,0 @@
var assign = require('object-assign')
var base = require('./webpack.config.base')
var constants = require('./constants')
var path = require('path')
module.exports = assign({}, base, {
devServer: {
contentBase: constants.EXAMPLES_DIRECTORY
},
entry: {
example: path.join(constants.EXAMPLES_DIRECTORY, 'index')
},
output: {
filename: 'examples.js',
path: constants.DIST_DIRECTORY
}
})

View File

@@ -1,19 +0,0 @@
var assign = require('object-assign')
var base = require('./webpack.config.base')
var constants = require('./constants')
module.exports = assign({}, base, {
entry: {
main: constants.SRC_DIRECTORY
},
externals: [{
'react': true,
'react-dom': true
}],
output: {
filename: 'react-native-web.js',
library: 'ReactNativeWeb',
libraryTarget: 'commonjs2',
path: constants.DIST_DIRECTORY
}
})

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

@@ -0,0 +1,63 @@
# 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;
styleElement: ReactComponent }`. `html` is the prerendered HTML, `style` is the
prerendered style sheet, and `styleElement` is a React Component. It's
recommended that you use `styleElement` to render the style sheet in an app
shell.
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!');
}
```

View File

@@ -1,11 +1,29 @@
# StyleSheet
React Native for Web will automatically vendor-prefix styles applied to the
library's components. The `StyleSheet` abstraction converts predefined styles
to CSS without a compile-time step. Some styles cannot be resolved outside of
the render loop and are applied as inline styles.
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).
Create a new StyleSheet:
## Methods
**create**(obj: {[key: string]: any})
Each key of the object passed to `create` must define a style object.
**flatten**: function
Flattens an array of styles into a single style object.
**renderToString**: function
Returns a string of CSS used to style the application.
## Properties
**hairlineWidth**: number
## Example
```js
const styles = StyleSheet.create({
@@ -20,12 +38,25 @@ const styles = StyleSheet.create({
},
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
@@ -36,130 +67,3 @@ Use styles:
/>
</View>
```
Render styles on the server or in the browser:
```js
StyleSheet.renderToString()
```
## Methods
**create**(obj: {[key: string]: any})
**destroy**()
Clears all style information.
**renderToString**()
Renders a CSS Style Sheet.
## About
### Strategy
React Native for Web uses a `style`-to-`className` conversion strategy that is
designed 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. Dead code elimination
4. Code minification
5. Sharing constants
6. Non-deterministic resolution
7. Breaking isolation
The strategy also minimizes the amount of generated CSS, making it more viable
to inline the style sheet when pre-rendering pages on the server. There is one
unique selector per unique style _declaration_.
```js
// definition
{
heading: {
color: 'gray',
fontSize: '2rem'
},
text: {
color: 'gray',
fontSize: '1.25rem'
}
}
// css
//
// .a { color: gray; }
// .b { font-size: 2rem; }
// .c { font-size: 1.25rem; }
```
For example:
```js
<View style={styles.root}>...</View>
const styles = StyleSheet.create({
root: {
background: 'transparent',
display: 'flex',
flexGrow: 1,
justifyContent: 'center'
}
})
```
Yields (in development):
```html
<div className="background:transparent display:flex flexGrow:1 justifyContent:center">...</div>
```
And is backed by the following CSS:
```css
.background\:transparent {background:transparent;}
.display\:flex {display:flex;}
.flexGrow\:1 {flex-grow:1;}
.justifyContext\:center {justify-content:center;}
```
In production the class names are obfuscated.
(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
style sheet 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.)
### Reset
React Native for Web includes a very small CSS reset taken from
[normalize.css](https://necolas.github.io/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;
}
```

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

@@ -57,30 +57,30 @@ could be an http address or a base64 encoded image.
**style**: style
[View](View.md) style
Defaults:
```js
{
alignSelf: 'flex-start',
backgroundColor: 'lightGray'
}
```
+ ...[View#style](View.md)
+ `resizeMode`
**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, StyleSheet } from 'react-native-web'
import React, { Component, Image, PropTypes, StyleSheet } from 'react-native'
const { Component, PropTypes } = React;
export default class Avatar extends Component {
export default class ImageExample extends Component {
constructor(props, context) {
super(props, context)
this.state = { loading: true }
@@ -112,7 +112,11 @@ export default class Avatar extends Component {
onLoad={this._onLoad.bind(this)}
resizeMode='cover'
source={{ uri: user.avatarUrl }}
style={{ ...styles.base, ...styles[size], ...loadingStyle }}
style={[
styles.base,
styles[size],
loadingStyle
]}
/>
)
}
@@ -121,23 +125,23 @@ export default class Avatar extends Component {
const styles = StyleSheet.create({
base: {
borderColor: 'white',
borderRadius: '5px',
borderWidth: '5px'
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
}
})
```

View File

@@ -10,28 +10,17 @@ Content to display over the image.
**style**: style
+ `property` type
Defaults:
```js
{
}
```
+ ...[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,13 +1,13 @@
# ScrollView
Scrollable `View` for use with bounded height, either by setting the height of
the view directly (discouraged) or by bounding the height of ancestor views.
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
Child content.
[...View props](./View.md)
**contentContainerStyle**: style
@@ -19,11 +19,34 @@ all of the child views.
When true, the scroll view's children are arranged horizontally in a row
instead of vertically in a column.
**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.
@@ -36,22 +59,38 @@ 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.
**style**: style
## Instance methods
[View](View.md) style
**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, StyleSheet } from 'react-native-web'
import React, { Component, ScrollView, StyleSheet } from 'react-native'
import Item from './Item'
export default class App extends React.Component {
export default class ScrollViewExample extends Component {
constructor(props, context) {
super(props, context)
this.state = {
items: Array.from({ length: 20 }).map((_, i) => ({ id: i }))
items: Array.from(new Array(20)).map((_, i) => ({ id: i }))
}
}
@@ -75,10 +114,10 @@ export default class App extends React.Component {
const styles = StyleSheet.create({
root: {
borderWidth: '1px'
borderWidth: 1
},
container: {
padding: '10px'
padding: 10
}
})
```

View File

@@ -1,13 +1,11 @@
# 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:
@@ -53,22 +51,22 @@ This function is called on press.
**style**: style
+ `backgroundColor`
+ ...[View#style](View.md)
+ `color`
+ `direction`
+ `fontFamily`
+ `fontSize`
+ `fontStyle`
+ `fontWeight`
+ `letterSpacing`
+ `lineHeight`
+ `margin`
+ `padding`
+ `textAlign`
+ `textDecoration`
+ `textAlignVertical`
+ `textDecorationLine`
+ `textShadow`
+ `textTransform`
+ `whiteSpace`
+ `wordWrap`
+ `writingDirection`
**testID**: string
@@ -77,22 +75,22 @@ Used to locate this view in end-to-end tests.
## Examples
```js
import React, { StyleSheet, 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;
@@ -100,32 +98,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 = StyleSheet.create({
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

@@ -48,9 +48,10 @@ updating the `value` prop to keep the controlled state in sync.
If `false`, text is not editable (i.e., read-only).
**keyboardType**: oneOf('default', 'email-address', 'numeric', 'phone-pad', 'url') = 'default'
**keyboardType**: oneOf('default', 'email-address', 'numeric', 'phone-pad', 'search', 'url', 'web-search') = 'default'
Determines which keyboard to open.
Determines which keyboard to open. (NOTE: Safari iOS requires an ancestral
`<form action>` element to display the `search` keyboard).
(Not available when `multiline` is `true`.)
@@ -111,11 +112,12 @@ object is passed as an argument to the callback handler.
**placeholder**: string
The string that will be rendered before text input has been entered.
The string that will be rendered in an empty `TextInput` before text has been
entered.
**placeholderTextColor**: string
TODO. The text color of the placeholder string.
The text color of the placeholder string.
**secureTextEntry**: bool = false
@@ -130,19 +132,8 @@ If `true`, all text will automatically be selected on focus.
**style**: style
[View](View.md) style
+ `color`
+ `direction`
+ `fontFamily`
+ `fontSize`
+ `fontStyle`
+ `fontWeight`
+ `letterSpacing`
+ `lineHeight`
+ `textAlign`
+ `textDecoration`
+ `textTransform`
+ ...[Text#style](Text.md)
+ `outline`
**testID**: string
@@ -156,17 +147,35 @@ 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, { StyleSheet, TextInput } from 'react-native-web'
import React, { Component, StyleSheet, TextInput } from 'react-native'
export default class AppTextInput extends React.Component {
export default class TextInputExample extends Component {
constructor(props, context) {
super(props, context)
this.state = { isFocused: false }
}
_onBlur(e) {
this.setState({ isFocused: false })
}
_onFocus(e) {
this.setState({ isFocused: true })
}
@@ -178,12 +187,13 @@ export default class AppTextInput extends React.Component {
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)
}}
style={[
styles.default
this.state.isFocused && styles.focused
]}
/>
);
}
@@ -192,7 +202,7 @@ export default class AppTextInput extends React.Component {
const styles = StyleSheet.create({
default: {
borderColor: 'gray',
borderWidth: '0 0 2px 0'
borderBottomWidth: 2
},
focused: {
borderColor: 'blue'

View File

@@ -1,104 +0,0 @@
# Touchable
A wrapper for making views respond to mouse, keyboard, and touch presses. On
press in, the touchable area can display a highlight color, and the opacity of
the wrapped view can be decreased.
This component combines the various `Touchable*` components from React Native.
Unsupported React Native props:
`accessibilityComponentType` (android) use `accessibilityRole`,
`accessibilityTraits` (ios) use `accessibilityRole`,
`onHideUnderlay` use `onPressOut`,
`onShowUnderlay` use `onPressIn`,
`underlayColor` use `activeUnderlayColor`
## Props
**accessibilityLabel**: string
Overrides the text that's read by the screen reader when the user interacts
with the element.
(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 screenreaders.
**activeOpacity**: number = 1
Sets the opacity of the child view when `onPressIn` is called. The opacity is
reset when `onPressOut` is called.
(web) **activeUnderlayColor**: string = 'transparent'
Sets the color of the background highlight when `onPressIn` is called. The
highlight is removed when `onPressOut` is called.
**children**: element
A single child element.
**delayLongPress**: number = 1000
Delay in ms, from `onPressIn`, before `onLongPress` is called.
**delayPressIn**: number = 0
(TODO)
Delay in ms, from the start of the touch, before `onPressIn` is called.
**delayPressOut**: number = 0
(TODO)
Delay in ms, from the release of the touch, before `onPressOut` is called.
**onLayout**: function
(TODO)
**onLongPress**: function
**onPress**: function
**onPressIn**: function
**onPressOut**: function
**style**: style
[View](View.md) style
## 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

@@ -4,21 +4,13 @@
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:
`accessibilityComponentType` (android) use `accessibilityRole`,
`accessibilityTraits` (ios) use `accessibilityRole`,
`collapsable` (android),
`importantForAccessibility` (android),
`needsOffscreenAlphaCompositing` (android),
`onAccessibilityTap`,
`onMagicTap`,
`onMoveShouldSetResponder`,
`onResponder*`,
`onStartShouldSetResponder`,
`onStartShouldSetResponderCapture`
`removeClippedSubviews` (ios),
`renderToHardwareTextureAndroid` (android),
`shouldRasterizeIOS` (ios)
`hitSlop`,
`onMagicTap`
## Props
@@ -58,6 +50,29 @@ implemented using `aria-hidden`.)
(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
@@ -100,6 +115,7 @@ from `style`.
+ `boxShadow`
+ `boxSizing`
+ `cursor`
+ `flex` (number)
+ `flexBasis`
+ `flexDirection`
+ `flexGrow`
@@ -108,7 +124,13 @@ from `style`.
+ `height`
+ `justifyContent`
+ `left`
+ `margin`
+ `margin` (single value)
+ `marginBottom`
+ `marginHorizontal`
+ `marginLeft`
+ `marginRight`
+ `marginTop`
+ `marginVertical`
+ `maxHeight`
+ `maxWidth`
+ `minHeight`
@@ -118,11 +140,18 @@ from `style`.
+ `overflow`
+ `overflowX`
+ `overflowY`
+ `padding`
+ `padding` (single value)
+ `paddingBottom`
+ `paddingHorizontal`
+ `paddingLeft`
+ `paddingRight`
+ `paddingTop`
+ `paddingVertical`
+ `position`
+ `right`
+ `top`
+ `transform`
+ `transformMatrix`
+ `userSelect`
+ `visibility`
+ `width`
@@ -155,11 +184,9 @@ Used to locate this view in end-to-end tests.
## Examples
```js
import React, { StyleSheet, 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}>
@@ -181,6 +208,4 @@ const styles = StyleSheet.create({
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,77 @@
# 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({
'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`:
```js
// 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, styleElement) => (
<html>
<head>
<meta charSet="utf-8" />
<meta content="initial-scale=1,width=device-width" name="viewport" />
{styleElement}
</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, styleElement } = AppRegistry.prerenderApplication('App', { initialProps })
// renders the full-page markup
const renderedApplicationHTML = React.renderToStaticMarkup(<AppShell html={html} styleElement={styleElement} />)
```

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

@@ -0,0 +1,220 @@
# 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
.__style1 { color: gray; }
.__style2 { font-size: 2rem; }
.__style3 { font-size: 1.25rem; }
```
Rendered HTML:
```html
<span className="__style1 __style2">Heading</span>
<span className="__style1 __style3">Text</span>
```
### 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%;
-webkit-tap-highlight-color:rgba(0,0,0,0)
}
body {
margin: 0;
}
button::-moz-focus-inner,
input::-moz-focus-inner {
border: 0;
padding: 0;
}
input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-decoration {
display: none;
}
```

324
examples/2048/Game2048.js Normal file
View File

@@ -0,0 +1,324 @@
/**
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* @providesModule Game2048
* @flow
*/
'use strict';
var React = require('react-native');
var {
Animated,
AppRegistry,
StyleSheet,
Text,
TouchableBounce,
View,
} = React;
var GameBoard = require('./GameBoard');
var BOARD_PADDING = 3;
var CELL_MARGIN = 4;
var CELL_SIZE = 60;
class Cell extends React.Component {
render() {
return <View style={styles.cell} />;
}
}
class Board extends React.Component {
render() {
return (
<View style={styles.board}>
<View style={styles.row}><Cell/><Cell/><Cell/><Cell/></View>
<View style={styles.row}><Cell/><Cell/><Cell/><Cell/></View>
<View style={styles.row}><Cell/><Cell/><Cell/><Cell/></View>
<View style={styles.row}><Cell/><Cell/><Cell/><Cell/></View>
{this.props.children}
</View>
);
}
}
class Tile extends React.Component {
state: any;
static _getPosition(index): number {
return BOARD_PADDING + (index * (CELL_SIZE + CELL_MARGIN * 2) + CELL_MARGIN);
}
constructor(props: {}) {
super(props);
var tile = this.props.tile;
this.state = {
opacity: new Animated.Value(0),
top: new Animated.Value(Tile._getPosition(tile.toRow())),
left: new Animated.Value(Tile._getPosition(tile.toColumn())),
};
}
calculateOffset(): {top: number; left: number; opacity: number} {
var tile = this.props.tile;
var offset = {
top: this.state.top,
left: this.state.left,
opacity: this.state.opacity,
};
if (tile.isNew()) {
Animated.timing(this.state.opacity, {
duration: 100,
toValue: 1,
}).start();
} else {
Animated.parallel([
Animated.timing(offset.top, {
duration: 100,
toValue: Tile._getPosition(tile.toRow()),
}),
Animated.timing(offset.left, {
duration: 100,
toValue: Tile._getPosition(tile.toColumn()),
}),
]).start();
}
return offset;
}
render() {
var tile = this.props.tile;
var tileStyles = [
styles.tile,
styles['tile' + tile.value],
this.calculateOffset(),
];
var textStyles = [
styles.value,
tile.value > 4 && styles.whiteText,
tile.value > 100 && styles.threeDigits,
tile.value > 1000 && styles.fourDigits,
];
return (
<Animated.View style={tileStyles}>
<Text style={textStyles}>{tile.value}</Text>
</Animated.View>
);
}
}
class GameEndOverlay extends React.Component {
render() {
var board = this.props.board;
if (!board.hasWon() && !board.hasLost()) {
return <View/>;
}
var message = board.hasWon() ?
'Good Job!' : 'Game Over';
return (
<View style={styles.overlay}>
<Text style={styles.overlayMessage}>{message}</Text>
<TouchableBounce onPress={this.props.onRestart} style={styles.tryAgain}>
<Text style={styles.tryAgainText}>Try Again?</Text>
</TouchableBounce>
</View>
);
}
}
class Game2048 extends React.Component {
startX: number;
startY: number;
state: any;
constructor(props: {}) {
super(props);
this.state = {
board: new GameBoard(),
};
this.startX = 0;
this.startY = 0;
}
restartGame() {
this.setState({board: new GameBoard()});
}
handleTouchStart(event: Object) {
if (this.state.board.hasWon()) {
return;
}
this.startX = event.nativeEvent.pageX;
this.startY = event.nativeEvent.pageY;
}
handleTouchEnd(event: Object) {
if (this.state.board.hasWon()) {
return;
}
var deltaX = event.nativeEvent.pageX - this.startX;
var deltaY = event.nativeEvent.pageY - this.startY;
var direction = -1;
if (Math.abs(deltaX) > 3 * Math.abs(deltaY) && Math.abs(deltaX) > 30) {
direction = deltaX > 0 ? 2 : 0;
} else if (Math.abs(deltaY) > 3 * Math.abs(deltaX) && Math.abs(deltaY) > 30) {
direction = deltaY > 0 ? 3 : 1;
}
if (direction !== -1) {
this.setState({board: this.state.board.move(direction)});
}
}
render() {
var tiles = this.state.board.tiles
.filter((tile) => tile.value)
.map((tile) => <Tile ref={tile.id} key={tile.id} tile={tile} />);
return (
<View
style={styles.container}
onTouchStart={(event) => this.handleTouchStart(event)}
onTouchEnd={(event) => this.handleTouchEnd(event)}>
<Board>
{tiles}
</Board>
<GameEndOverlay board={this.state.board} onRestart={() => this.restartGame()} />
</View>
);
}
}
var styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
board: {
padding: BOARD_PADDING,
backgroundColor: '#bbaaaa',
borderRadius: 5,
},
overlay: {
position: 'absolute',
top: 0,
bottom: 0,
left: 0,
right: 0,
backgroundColor: 'rgba(221, 221, 221, 0.5)',
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
},
overlayMessage: {
fontSize: 40,
marginBottom: 20,
},
tryAgain: {
backgroundColor: '#887761',
padding: 20,
borderRadius: 5,
},
tryAgainText: {
color: '#ffffff',
fontSize: 20,
fontWeight: '500',
},
cell: {
width: CELL_SIZE,
height: CELL_SIZE,
borderRadius: 5,
backgroundColor: '#ddccbb',
margin: CELL_MARGIN,
},
row: {
flexDirection: 'row',
},
tile: {
position: 'absolute',
width: CELL_SIZE,
height: CELL_SIZE,
backgroundColor: '#ddccbb',
borderRadius: 5,
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
value: {
fontSize: 24,
color: '#776666',
fontFamily: 'Verdana',
fontWeight: '500',
},
tile2: {
backgroundColor: '#eeeeee',
},
tile4: {
backgroundColor: '#eeeecc',
},
tile8: {
backgroundColor: '#ffbb87',
},
tile16: {
backgroundColor: '#ff9966',
},
tile32: {
backgroundColor: '#ff7755',
},
tile64: {
backgroundColor: '#ff5533',
},
tile128: {
backgroundColor: '#eecc77',
},
tile256: {
backgroundColor: '#eecc66',
},
tile512: {
backgroundColor: '#eecc55',
},
tile1024: {
backgroundColor: '#eecc33',
},
tile2048: {
backgroundColor: '#eecc22',
},
whiteText: {
color: '#ffffff',
},
threeDigits: {
fontSize: 20,
},
fourDigits: {
fontSize: 18,
},
});
AppRegistry.registerComponent('Game2048', () => Game2048);
module.exports = Game2048;

201
examples/2048/GameBoard.js Normal file
View File

@@ -0,0 +1,201 @@
/**
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* @providesModule GameBoard
* @flow
*/
'use strict';
// NB: Taken straight from: https://github.com/IvanVergiliev/2048-react/blob/master/src/board.js
// with no modification except to format it for CommonJS and fix lint/flow errors
var rotateLeft = function (matrix) {
var rows = matrix.length;
var columns = matrix[0].length;
var res = [];
for (var row = 0; row < rows; ++row) {
res.push([]);
for (var column = 0; column < columns; ++column) {
res[row][column] = matrix[column][columns - row - 1];
}
}
return res;
};
var Tile = function (value?: number, row?: number, column?: number) {
this.value = value || 0;
this.row = row || -1;
this.column = column || -1;
this.oldRow = -1;
this.oldColumn = -1;
this.markForDeletion = false;
this.mergedInto = null;
this.id = Tile.id++;
};
Tile.id = 0;
Tile.prototype.moveTo = function (row, column) {
this.oldRow = this.row;
this.oldColumn = this.column;
this.row = row;
this.column = column;
};
Tile.prototype.isNew = function () {
return this.oldRow === -1 && !this.mergedInto;
};
Tile.prototype.hasMoved = function () {
return (this.fromRow() !== -1 && (this.fromRow() !== this.toRow() || this.fromColumn() !== this.toColumn())) ||
this.mergedInto;
};
Tile.prototype.fromRow = function () {
return this.mergedInto ? this.row : this.oldRow;
};
Tile.prototype.fromColumn = function () {
return this.mergedInto ? this.column : this.oldColumn;
};
Tile.prototype.toRow = function () {
return this.mergedInto ? this.mergedInto.row : this.row;
};
Tile.prototype.toColumn = function () {
return this.mergedInto ? this.mergedInto.column : this.column;
};
var Board = function () {
this.tiles = [];
this.cells = [];
for (var i = 0; i < Board.size; ++i) {
this.cells[i] = [this.addTile(), this.addTile(), this.addTile(), this.addTile()];
}
this.addRandomTile();
this.setPositions();
this.won = false;
};
Board.prototype.addTile = function () {
var res = new Tile();
Tile.apply(res, arguments);
this.tiles.push(res);
return res;
};
Board.size = 4;
Board.prototype.moveLeft = function () {
var hasChanged = false;
for (var row = 0; row < Board.size; ++row) {
var currentRow = this.cells[row].filter(function (tile) { return tile.value !== 0; });
var resultRow = [];
for (var target = 0; target < Board.size; ++target) {
var targetTile = currentRow.length ? currentRow.shift() : this.addTile();
if (currentRow.length > 0 && currentRow[0].value === targetTile.value) {
var tile1 = targetTile;
targetTile = this.addTile(targetTile.value);
tile1.mergedInto = targetTile;
var tile2 = currentRow.shift();
tile2.mergedInto = targetTile;
targetTile.value += tile2.value;
}
resultRow[target] = targetTile;
this.won = this.won || (targetTile.value === 2048);
hasChanged = hasChanged || (targetTile.value !== this.cells[row][target].value);
}
this.cells[row] = resultRow;
}
return hasChanged;
};
Board.prototype.setPositions = function () {
this.cells.forEach(function (row, rowIndex) {
row.forEach(function (tile, columnIndex) {
tile.oldRow = tile.row;
tile.oldColumn = tile.column;
tile.row = rowIndex;
tile.column = columnIndex;
tile.markForDeletion = false;
});
});
};
Board.fourProbability = 0.1;
Board.prototype.addRandomTile = function () {
var emptyCells = [];
for (var r = 0; r < Board.size; ++r) {
for (var c = 0; c < Board.size; ++c) {
if (this.cells[r][c].value === 0) {
emptyCells.push({r: r, c: c});
}
}
}
var index = Math.floor(Math.random() * emptyCells.length);
var cell = emptyCells[index];
var newValue = Math.random() < Board.fourProbability ? 4 : 2;
this.cells[cell.r][cell.c] = this.addTile(newValue);
};
Board.prototype.move = function (direction) {
// 0 -> left, 1 -> up, 2 -> right, 3 -> down
this.clearOldTiles();
for (var i = 0; i < direction; ++i) {
this.cells = rotateLeft(this.cells);
}
var hasChanged = this.moveLeft();
for (var i = direction; i < 4; ++i) {
this.cells = rotateLeft(this.cells);
}
if (hasChanged) {
this.addRandomTile();
}
this.setPositions();
return this;
};
Board.prototype.clearOldTiles = function () {
this.tiles = this.tiles.filter(function (tile) { return tile.markForDeletion === false; });
this.tiles.forEach(function (tile) { tile.markForDeletion = true; });
};
Board.prototype.hasWon = function () {
return this.won;
};
Board.deltaX = [-1, 0, 1, 0];
Board.deltaY = [0, -1, 0, 1];
Board.prototype.hasLost = function () {
var canMove = false;
for (var row = 0; row < Board.size; ++row) {
for (var column = 0; column < Board.size; ++column) {
canMove = canMove || (this.cells[row][column].value === 0);
for (var dir = 0; dir < 4; ++dir) {
var newRow = row + Board.deltaX[dir];
var newColumn = column + Board.deltaY[dir];
if (newRow < 0 || newRow >= Board.size || newColumn < 0 || newColumn >= Board.size) {
continue;
}
canMove = canMove || (this.cells[row][column].value === this.cells[newRow][newColumn].value);
}
}
}
return !canMove;
};
module.exports = Board;

View File

@@ -0,0 +1,321 @@
/**
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* @providesModule TicTacToeApp
* @flow
*/
'use strict';
var React = require('react-native');
var {
AppRegistry,
StyleSheet,
Text,
TouchableHighlight,
View,
} = React;
class Board {
grid: Array<Array<number>>;
turn: number;
constructor() {
var size = 3;
var grid = Array(size);
for (var i = 0; i < size; i++) {
var row = Array(size);
for (var j = 0; j < size; j++) {
row[j] = 0;
}
grid[i] = row;
}
this.grid = grid;
this.turn = 1;
}
mark(row: number, col: number, player: number): Board {
this.grid[row][col] = player;
return this;
}
hasMark(row: number, col: number): boolean {
return this.grid[row][col] !== 0;
}
winner(): ?number {
for (var i = 0; i < 3; i++) {
if (this.grid[i][0] !== 0 && this.grid[i][0] === this.grid[i][1] &&
this.grid[i][0] === this.grid[i][2]) {
return this.grid[i][0];
}
}
for (var i = 0; i < 3; i++) {
if (this.grid[0][i] !== 0 && this.grid[0][i] === this.grid[1][i] &&
this.grid[0][i] === this.grid[2][i]) {
return this.grid[0][i];
}
}
if (this.grid[0][0] !== 0 && this.grid[0][0] === this.grid[1][1] &&
this.grid[0][0] === this.grid[2][2]) {
return this.grid[0][0];
}
if (this.grid[0][2] !== 0 && this.grid[0][2] === this.grid[1][1] &&
this.grid[0][2] === this.grid[2][0]) {
return this.grid[0][2];
}
return null;
}
tie(): boolean {
for (var i = 0; i < 3; i++) {
for (var j = 0; j < 3; j++) {
if (this.grid[i][j] === 0) {
return false;
}
}
}
return this.winner() === null;
}
}
var Cell = React.createClass({
cellStyle() {
switch (this.props.player) {
case 1:
return styles.cellX;
case 2:
return styles.cellO;
default:
return null;
}
},
textStyle() {
switch (this.props.player) {
case 1:
return styles.cellTextX;
case 2:
return styles.cellTextO;
default:
return {};
}
},
textContents() {
switch (this.props.player) {
case 1:
return 'X';
case 2:
return 'O';
default:
return '';
}
},
render() {
return (
<TouchableHighlight
onPress={this.props.onPress}
underlayColor="transparent"
activeOpacity={0.5}>
<View style={[styles.cell, this.cellStyle()]}>
<Text style={[styles.cellText, this.textStyle()]}>
{this.textContents()}
</Text>
</View>
</TouchableHighlight>
);
}
});
var GameEndOverlay = React.createClass({
render() {
var board = this.props.board;
var tie = board.tie();
var winner = board.winner();
if (!winner && !tie) {
return <View />;
}
var message;
if (tie) {
message = 'It\'s a tie!';
} else {
message = (winner === 1 ? 'X' : 'O') + ' wins!';
}
return (
<View style={styles.overlay}>
<Text style={styles.overlayMessage}>{message}</Text>
<TouchableHighlight
onPress={this.props.onRestart}
underlayColor="transparent"
activeOpacity={0.5}>
<View style={styles.newGame}>
<Text style={styles.newGameText}>New Game</Text>
</View>
</TouchableHighlight>
</View>
);
}
});
var TicTacToeApp = React.createClass({
getInitialState() {
return { board: new Board(), player: 1 };
},
restartGame() {
this.setState(this.getInitialState());
},
nextPlayer(): number {
return this.state.player === 1 ? 2 : 1;
},
handleCellPress(row: number, col: number) {
if (this.state.board.hasMark(row, col)) {
return;
}
this.setState({
board: this.state.board.mark(row, col, this.state.player),
player: this.nextPlayer(),
});
},
render() {
var rows = this.state.board.grid.map((cells, row) =>
<View key={'row' + row} style={styles.row}>
{cells.map((player, col) =>
<Cell
key={'cell' + col}
player={player}
onPress={this.handleCellPress.bind(this, row, col)}
/>
)}
</View>
);
return (
<View style={styles.container}>
<Text style={styles.title}>EXTREME T3</Text>
<View style={styles.board}>
{rows}
</View>
<GameEndOverlay
board={this.state.board}
onRestart={this.restartGame}
/>
</View>
);
}
});
var styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'white'
},
title: {
fontFamily: 'Chalkduster',
fontSize: 39,
marginBottom: 20,
},
board: {
padding: 5,
backgroundColor: '#47525d',
borderRadius: 10,
},
row: {
flexDirection: 'row',
},
// CELL
cell: {
width: 80,
height: 80,
borderRadius: 5,
backgroundColor: '#7b8994',
margin: 5,
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
cellX: {
backgroundColor: '#72d0eb',
},
cellO: {
backgroundColor: '#7ebd26',
},
// CELL TEXT
cellText: {
borderRadius: 5,
fontSize: 50,
fontFamily: 'AvenirNext-Bold',
},
cellTextX: {
color: '#19a9e5',
},
cellTextO: {
color: '#b9dc2f',
},
// GAME OVER
overlay: {
position: 'absolute',
top: 0,
bottom: 0,
left: 0,
right: 0,
backgroundColor: 'rgba(221, 221, 221, 0.5)',
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
},
overlayMessage: {
fontSize: 40,
marginBottom: 20,
marginLeft: 20,
marginRight: 20,
fontFamily: 'AvenirNext-DemiBold',
textAlign: 'center',
},
newGame: {
backgroundColor: '#887765',
padding: 20,
borderRadius: 5,
},
newGameText: {
color: 'white',
fontSize: 20,
fontFamily: 'AvenirNext-DemiBold',
},
});
AppRegistry.registerComponent('TicTacToeApp', () => TicTacToeApp);
module.exports = TicTacToeApp;

View File

@@ -1,7 +1,7 @@
import GridView from './GridView'
import Heading from './Heading'
import MediaQueryWidget from './MediaQueryWidget'
import React, { Image, StyleSheet, ScrollView, Text, TextInput, Touchable, View } from '../../src'
import React, { Image, StyleSheet, ScrollView, Text, TextInput, TouchableHighlight, View } from 'react-native'
export default class App extends React.Component {
static propTypes = {
@@ -18,14 +18,15 @@ export default class App extends React.Component {
render() {
const { mediaQuery } = this.props
const rootStyles = {
...(styles.root.common),
...(mediaQuery.small.matches && styles.root.mqSmall),
...(mediaQuery.large.matches && styles.root.mqLarge)
}
const finalRootStyles = [
rootStyles.common,
mediaQuery.small.matches && rootStyles.mqSmall,
mediaQuery.large.matches && rootStyles.mqLarge
]
return (
<View accessibilityRole='main' style={rootStyles}>
<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
@@ -95,10 +96,10 @@ export default class App extends React.Component {
/>
<TextInput secureTextEntry />
<TextInput defaultValue='read only' editable={false} />
<TextInput keyboardType='email-address' />
<TextInput keyboardType='email-address' placeholder='you@domain.com' placeholderTextColor='red' />
<TextInput keyboardType='numeric' />
<TextInput keyboardType='phone-pad' />
<TextInput keyboardType='url' selectTextOnFocus />
<TextInput defaultValue='https://delete-me' keyboardType='url' placeholder='https://www.some-website.com' selectTextOnFocus />
<TextInput
defaultValue='default value'
maxNumberOfLines={10}
@@ -107,7 +108,7 @@ export default class App extends React.Component {
/>
<Heading size='large'>Touchable</Heading>
<Touchable
<TouchableHighlight
accessibilityLabel={'Touchable element'}
activeHighlight='lightblue'
activeOpacity={0.8}
@@ -119,7 +120,7 @@ export default class App extends React.Component {
<View style={styles.touchableArea}>
<Text>Touchable area (press, long press)</Text>
</View>
</Touchable>
</TouchableHighlight>
<Heading size='large'>View</Heading>
<Heading>Default layout</Heading>
@@ -199,29 +200,32 @@ export default class App extends React.Component {
style={styles.scrollViewStyle}
>
{Array.from({ length: 50 }).map((item, i) => (
<View key={i} style={{...styles.box, ...styles.horizontalBox}}>
<View key={i} style={[ styles.box, styles.horizontalBox ]}>
<Text>{i}</Text>
</View>
))}
</ScrollView>
</View>
</View>
</View>
</ScrollView>
)
}
}
const styles = StyleSheet.create({
root: {
common: {
margin: '0 auto'
},
mqSmall: {
maxWidth: '400px'
},
mqLarge: {
maxWidth: '600px'
}
const rootStyles = StyleSheet.create({
common: {
marginVertical: 0,
marginHorizontal: 'auto'
},
mqSmall: {
maxWidth: '400px'
},
mqLarge: {
maxWidth: '600px'
}
})
const styles = StyleSheet.create({
row: {
flexDirection: 'row',
flexWrap: 'wrap'
@@ -230,7 +234,7 @@ const styles = StyleSheet.create({
alignItems: 'center',
flexGrow: 1,
justifyContent: 'center',
borderWidth: '1px'
borderWidth: 1
},
horizontalBox: {
width: '50px'

View File

@@ -1,20 +1,4 @@
import React, { StyleSheet, View } from '../../src'
const { Component, PropTypes } = React
const styles = StyleSheet.create({
root: {
overflow: 'hidden'
},
contentContainer: {
flexDirection: 'row',
flexGrow: 1
},
// distribute all space (rather than extra space)
column: {
flexBasis: '0%'
}
})
import React, { Component, PropTypes, StyleSheet, View } from 'react-native'
export default class GridView extends Component {
static propTypes = {
@@ -42,8 +26,8 @@ export default class GridView extends Component {
const contentContainerStyle = {
...styles.contentContainer,
margin: `0 calc(-0.5 * ${alley})`,
padding: `0 ${gutter}`
marginHorizontal: `calc(-0.5 * ${alley})`,
paddingHorizontal: `${gutter}`
}
const newChildren = React.Children.map(children, (child) => {
@@ -51,7 +35,7 @@ export default class GridView extends Component {
style: {
...child.props.style,
...styles.column,
margin: `0 calc(0.5 * ${alley})`
marginHorizontal: `calc(0.5 * ${alley})`
}
})
})
@@ -65,3 +49,17 @@ export default class GridView extends Component {
)
}
}
const styles = StyleSheet.create({
root: {
overflow: 'hidden'
},
contentContainer: {
flexDirection: 'row',
flexGrow: 1
},
// distribute all space (rather than extra space)
column: {
flexBasis: '0%'
}
})

View File

@@ -1,31 +1,34 @@
import React from 'react'
import { StyleSheet, Text } from '../../src'
const headingStyles = StyleSheet.create({
size: {
xlarge: {
fontSize: '2rem',
marginBottom: '1em'
},
large: {
fontSize: '1.5rem',
marginBottom: '1em',
marginTop: '1em'
},
normal: {
fontSize: '1.25rem',
marginBottom: '0.5em',
marginTop: '0.5em'
}
}
})
import React, { StyleSheet, Text } from 'react-native'
const Heading = ({ children, size = 'normal' }) => (
<Text
accessibilityRole='heading'
children={children}
style={headingStyles.size[size]}
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

@@ -1,18 +1,4 @@
import React, { StyleSheet, Text, View } from '../../src'
const styles = StyleSheet.create({
root: {
alignItems: 'center',
borderWidth: 1,
margin: '10px 0',
padding: 10,
textAlign: 'center'
},
heading: {
fontWeight: 'bold',
padding: 5
}
})
import React, { StyleSheet, Text, View } from 'react-native'
const MediaQueryWidget = ({ mediaQuery = {} }) => {
const active = Object.keys(mediaQuery).reduce((active, alias) => {
@@ -28,9 +14,26 @@ const MediaQueryWidget = ({ mediaQuery = {} }) => {
return (
<View style={styles.root}>
<Text style={styles.heading}>Active Media Query</Text>
<Text>{`"${active.alias}"`} {active.mql.media}</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

@@ -2,8 +2,5 @@
<meta charset="utf-8">
<title>React Native for Web</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="The core React Native components adapted and expanded upon for the web">
<style>html { font-family: sans-serif; }</style>
<style id="react-stylesheet"></style>
<div id="react-root"></div>
<script src="/examples.js"></script>
<script src="/bundle.js"></script>

View File

@@ -1,22 +1,7 @@
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, { StyleSheet } from '../src'
import ReactDOM from 'react-dom'
import React, { AppRegistry } from 'react-native'
import Game2048 from './2048/Game2048'
import TicTacToeApp from './TicTacToe/TicTacToe'
const mediaQueries = {
small: '(min-width: 300px)',
medium: '(min-width: 400px)',
large: '(min-width: 500px)'
}
const ResponsiveApp = matchMedia()(App)
ReactDOM.render(
<MediaProvider getMedia={createGetter(mediaQueries)} listener={createListener(mediaQueries)}>
<ResponsiveApp />
</MediaProvider>,
document.getElementById('react-root')
)
document.getElementById('react-stylesheet').textContent = StyleSheet.renderToString()
AppRegistry.runApplication('Game2048', {
rootTag: document.getElementById('react-root')
})

View File

@@ -0,0 +1,38 @@
const path = require('path')
const webpack = require('webpack')
const EXAMPLES_DIRECTORY = __dirname
module.exports = {
devServer: {
contentBase: EXAMPLES_DIRECTORY
},
entry: {
example: EXAMPLES_DIRECTORY
},
module: {
loaders: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
query: { cacheDirectory: true }
}
]
},
output: {
filename: 'bundle.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,9 +1,9 @@
var constants = require('./constants')
var webpack = require('webpack')
var testEntry = 'tests.webpack.js'
module.exports = function (config) {
config.set({
basePath: constants.ROOT_DIRECTORY,
browsers: process.env.TRAVIS ? [ 'Firefox' ] : [ 'Chrome' ],
browserNoActivityTimeout: 60000,
client: {
@@ -12,21 +12,21 @@ module.exports = function (config) {
useIframe: true
},
files: [
constants.TEST_ENTRY
testEntry
],
frameworks: [ 'mocha' ],
plugins: [
'karma-chrome-launcher',
'karma-firefox-launcher',
'karma-mocha',
'karma-mocha-reporter',
'karma-sourcemap-loader',
'karma-spec-reporter',
'karma-webpack'
],
preprocessors: {
[constants.TEST_ENTRY]: [ 'webpack', 'sourcemap' ]
[testEntry]: [ 'webpack', 'sourcemap' ]
},
reporters: process.env.TRAVIS ? [ 'dots' ] : [ 'mocha' ],
reporters: process.env.TRAVIS ? [ 'dots' ] : [ 'spec' ],
singleRun: true,
webpack: {
devtool: 'inline-source-map',

View File

@@ -1,56 +1,65 @@
{
"name": "react-native-web",
"version": "0.0.9",
"version": "0.0.20",
"description": "React Native for Web",
"main": "dist/react-native-web.js",
"main": "dist/index.js",
"files": [
"dist"
],
"scripts": {
"build": "rm -rf ./dist && webpack --config config/webpack.config.publish.js --sort-assets-by --progress",
"examples": "webpack-dev-server --config config/webpack.config.example.js --inline --hot --colors --quiet",
"lint": "eslint config examples src",
"prepublish": "NODE_ENV=publish npm run build",
"test": "npm run lint && npm run test:unit",
"test:unit": "karma start config/karma.config.js",
"test:watch": "npm run test:unit -- --no-single-run"
"build": "rm -rf ./dist && mkdir dist && babel src -d dist --ignore **/__tests__,src/modules/specHelpers",
"build:umd": "webpack --config webpack.config.js --sort-assets-by --progress",
"examples": "webpack-dev-server --config examples/webpack.config.js --inline --hot --colors --quiet",
"lint": "eslint src",
"prepublish": "npm run build && npm run build:umd",
"test": "karma start karma.config.js",
"test:watch": "npm run test -- --no-single-run"
},
"dependencies": {
"inline-style-prefixer": "^0.3.3",
"lodash.debounce": "^3.1.1",
"react-tappable": "^0.7.1",
"react-textarea-autosize": "^3.0.0"
"fbjs": "0.6.x || 0.7.x",
"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": {
"babel-core": "^5.8.25",
"babel-eslint": "^4.1.3",
"babel-loader": "^5.3.2",
"babel-runtime": "^5.8.25",
"eslint": "^1.7.1",
"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.1.0",
"eslint-plugin-react": "^3.6.0",
"eslint-config-standard-react": "^1.2.1",
"eslint-plugin-react": "^3.13.1",
"eslint-plugin-standard": "^1.3.1",
"karma": "^0.13.11",
"karma-browserstack-launcher": "^0.1.5",
"karma-chrome-launcher": "^0.2.1",
"karma-firefox-launcher": "^0.1.6",
"karma-mocha": "^0.2.0",
"karma-mocha-reporter": "^1.1.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.3",
"mocha": "^2.3.4",
"node-libs-browser": "^0.5.3",
"object-assign": "^4.0.1",
"react": "^0.14.0",
"react-addons-test-utils": "^0.14.0",
"react-dom": "^0.14.0",
"react-media-queries": "^2.0.0",
"webpack": "^1.12.2",
"webpack-dev-server": "^1.12.1"
"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"

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,20 @@
/* eslint-env mocha */
import assert from 'assert'
import React from 'react'
import { elementId } from '../../StyleSheet'
import { prerenderApplication } from '../renderApplication'
const component = () => <div />
suite('apis/AppRegistry/renderApplication', () => {
test('prerenderApplication', () => {
const { html, style, styleElement } = prerenderApplication(component, {})
assert.ok(html.indexOf('<div ') > -1)
assert.ok(typeof style === 'string')
assert.equal(styleElement.type, 'style')
assert.equal(styleElement.props.id, elementId)
assert.equal(styleElement.props.dangerouslySetInnerHTML.__html, style)
})
})

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,48 @@
/**
* 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 = () => StyleSheet.renderToString()
const styleAsElement = (style) => <style dangerouslySetInnerHTML={{ __html: style }} id={StyleSheet.elementId} />
const styleAsTagString = (style) => `<style id="${StyleSheet.elementId}">${style}</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', styleAsTagString(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()
const styleElement = styleAsElement(style)
return { html, style, styleElement }
}

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,36 @@
/**
* Copyright (c) 2015-present, Nicolas Gallagher.
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* @flow
*/
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment'
import invariant from 'fbjs/lib/invariant'
const win = ExecutionEnvironment.canUseDOM ? window : { screen: {} }
const dimensions = {
screen: {
fontScale: 1,
get height() { return win.screen.height },
scale: win.devicePixelRatio || 1,
get width() { return win.screen.width }
},
window: {
fontScale: 1,
get height() { return win.innerHeight },
scale: win.devicePixelRatio || 1,
get width() { return win.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', () => {})
})

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

@@ -0,0 +1,85 @@
/**
* Copyright (c) 2015-present, Nicolas Gallagher.
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* @flow
*/
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment'
import invariant from 'fbjs/lib/invariant'
const connection = ExecutionEnvironment.canUseDOM && (
window.navigator.connection ||
window.navigator.mozConnection ||
window.navigator.webkitConnection
)
const eventTypes = [ 'change' ]
/**
* 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,59 @@
// based on https://github.com/facebook/react/pull/4303/files
import EventConstants from 'react/lib/EventConstants'
import EventPluginRegistry from 'react/lib/EventPluginRegistry'
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment'
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 = ExecutionEnvironment.canUseDOM && (
'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,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,101 @@
/**
* Copyright (c) 2016-present, Nicolas Gallagher.
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* @flow
*/
import prefixAll from 'inline-style-prefix-all'
import hyphenate from './hyphenate'
import expandStyle from './expandStyle'
import flattenStyle from './flattenStyle'
import processTransform from './processTransform'
import { predefinedClassNames } from './predefs'
let stylesCache = {}
let uniqueID = 0
const getCacheKey = (prop, value) => `${prop}:${value}`
const normalizeStyle = (style) => {
return processTransform(expandStyle(flattenStyle(style)))
}
const createCssDeclarations = (style) => {
return Object.keys(style).map((prop) => {
const property = hyphenate(prop)
const value = style[prop]
return `${property}:${value};`
}).sort().join('')
}
class StyleSheetRegistry {
/* for testing */
static _reset() {
stylesCache = {}
uniqueID = 0
}
static renderToString() {
let str = `/* ${uniqueID} unique declarations */`
return Object.keys(stylesCache).reduce((str, key) => {
const id = stylesCache[key].id
const style = stylesCache[key].style
const declarations = createCssDeclarations(style)
const rule = `\n.${id}{${declarations}}`
str += rule
return str
}, str)
}
static registerStyle(style: Object): number {
if (process.env.NODE_ENV !== 'production') {
Object.freeze(style)
}
const normalizedStyle = normalizeStyle(style)
Object.keys(normalizedStyle).forEach((prop) => {
const value = normalizedStyle[prop]
const cacheKey = getCacheKey(prop, value)
const exists = stylesCache[cacheKey] && stylesCache[cacheKey].id
if (!exists) {
const id = ++uniqueID
// add new declaration to the store
stylesCache[cacheKey] = {
id: `__style${id}`,
style: prefixAll({ [prop]: value })
}
}
})
return style
}
static getStyleAsNativeProps(styleSheetObject, canUseCSS = false) {
const classList = []
const normalizedStyle = normalizeStyle(styleSheetObject)
let style = {}
for (const prop in normalizedStyle) {
const value = normalizedStyle[prop]
const cacheKey = getCacheKey(prop, value)
let selector = stylesCache[cacheKey] && stylesCache[cacheKey].id || predefinedClassNames[cacheKey]
if (selector && canUseCSS) {
classList.push(selector)
} else {
style[prop] = normalizedStyle[prop]
}
}
return {
className: classList.join(' '),
style: prefixAll(style)
}
}
}
module.exports = StyleSheetRegistry

View File

@@ -0,0 +1,69 @@
/**
* 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
})
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,55 @@
/* 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 StyleSheetRegistry from '../StyleSheetRegistry'
suite('apis/StyleSheet/StyleSheetRegistry', () => {
setup(() => {
StyleSheetRegistry._reset()
})
test('static renderToString', () => {
const style1 = { alignItems: 'center', opacity: 1 }
const style2 = { alignItems: 'center', opacity: 1 }
StyleSheetRegistry.registerStyle(style1)
StyleSheetRegistry.registerStyle(style2)
const actual = StyleSheetRegistry.renderToString()
const expected = `/* 2 unique declarations */
.__style1{-ms-flex-align:center;-webkit-align-items:center;-webkit-box-align:center;align-items:center;}
.__style2{opacity:1;}`
assert.equal(actual, expected)
})
test('static getStyleAsNativeProps', () => {
const style = { borderColorTop: 'white', opacity: 1 }
const style1 = { opacity: 1 }
StyleSheetRegistry.registerStyle(style1)
// canUseCSS = false
const actual1 = StyleSheetRegistry.getStyleAsNativeProps(style)
const expected1 = {
className: '',
style: { borderColorTop: 'white', opacity: 1 }
}
assert.deepEqual(actual1, expected1)
// canUseCSS = true
const actual2 = StyleSheetRegistry.getStyleAsNativeProps(style, true)
const expected2 = {
className: '__style1',
style: { borderColorTop: 'white' }
}
assert.deepEqual(actual2, expected2)
})
})

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

@@ -3,7 +3,7 @@
import assert from 'assert'
import hyphenate from '../hyphenate'
suite('modules/StyleSheet/hyphenate', () => {
suite('apis/StyleSheet/hyphenate', () => {
test('style property', () => {
assert.equal(hyphenate('alignItems'), 'align-items')
assert.equal(hyphenate('color'), 'color')

View File

@@ -0,0 +1,51 @@
/* eslint-env mocha */
import { resetCSS, predefinedCSS } from '../predefs'
import assert from 'assert'
import StyleSheet from '..'
const styles = { root: { opacity: 1 } }
suite('apis/StyleSheet', () => {
setup(() => {
StyleSheet._destroy()
})
suite('create', () => {
test('returns styles object', () => {
assert.equal(StyleSheet.create(styles), styles)
})
test('updates already-rendered style sheet', () => {
// setup
const div = document.createElement('div')
document.body.appendChild(div)
StyleSheet.create(styles)
div.innerHTML = `<style id='${StyleSheet.elementId}'>${StyleSheet.renderToString()}</style>`
// test
StyleSheet.create({ root: { color: 'red' } })
assert.equal(
document.getElementById(StyleSheet.elementId).textContent,
`${resetCSS}\n${predefinedCSS}\n` +
`/* 2 unique declarations */\n` +
`.__style1{opacity:1;}\n` +
`.__style2{color:red;}`
)
// teardown
document.body.removeChild(div)
})
})
test('renderToString', () => {
StyleSheet.create(styles)
assert.equal(
StyleSheet.renderToString(),
`${resetCSS}\n${predefinedCSS}\n` +
`/* 1 unique declarations */\n` +
`.__style1{opacity:1;}`
)
})
})

View File

@@ -3,7 +3,7 @@
import assert from 'assert'
import normalizeValue from '../normalizeValue'
suite('modules/StyleSheet/normalizeValue', () => {
suite('apis/StyleSheet/normalizeValue', () => {
test('normalizes property values requiring units', () => {
assert.deepEqual(normalizeValue('margin', 0), '0px')
})

View File

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

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,65 @@
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' ],
overflow: [ 'overflowX', 'overflowY' ],
padding: [ 'paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft' ],
paddingHorizontal: [ 'paddingRight', 'paddingLeft' ],
paddingVertical: [ 'paddingTop', 'paddingBottom' ],
textDecorationLine: [ 'textDecoration' ],
writingDirection: [ 'direction' ]
}
/**
* Alpha-sort properties, apart from shorthands they must appear before the
* longhand properties that they expand into. This lets more specific styles
* override less specific styles, 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])
// React Native treats `flex:1` like `flex:1 1 auto`
if (key === 'flex') {
resolvedStyle.flexGrow = value
resolvedStyle.flexShrink = 1
resolvedStyle.flexBasis = 'auto'
} else if (key === 'textAlignVertical') {
resolvedStyle.verticalAlign = (value === 'center' ? 'middle' : value)
} else if (expandedProps) {
expandedProps.forEach((prop, i) => {
resolvedStyle[expandedProps[i]] = value
})
} else {
resolvedStyle[key] = value
}
return resolvedStyle
}, {})
}
module.exports = expandStyle

View File

@@ -0,0 +1,31 @@
/**
* Copyright (c) 2016-present, Nicolas Gallagher.
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* @flow
*/
import invariant from 'fbjs/lib/invariant'
module.exports = function flattenStyle(style): ?Object {
if (!style) {
return undefined
}
invariant(style !== true, 'style may be false but not true')
if (!Array.isArray(style)) {
return 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,71 @@
import { resetCSS, predefinedCSS } from './predefs'
import flattenStyle from './flattenStyle'
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
*/
/**
* Destroy existing styles
*/
const _destroy = () => {
isRendered = false
StyleSheetRegistry._reset()
}
const create = (styles: Object): Object => {
for (const key in styles) {
StyleSheetValidation.validateStyle(key, styles)
StyleSheetRegistry.registerStyle(styles[key])
}
// 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 "${ELEMENT_ID}" element`)
}
}
return styles
}
/**
* Render the styles as a CSS style sheet
*/
const renderToString = () => {
const css = StyleSheetRegistry.renderToString()
isRendered = true
return `${resetCSS}\n${predefinedCSS}\n${css}`
}
/**
* Accepts React props and converts inline styles to single purpose classes
* where possible.
*/
const resolve = ({ style = {} }) => {
return StyleSheetRegistry.getStyleAsNativeProps(style, isRendered)
}
module.exports = {
_destroy,
create,
elementId: ELEMENT_ID,
hairlineWidth: 1,
flatten: flattenStyle,
renderToString,
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;

View File

@@ -23,11 +23,11 @@ const unitlessNumbers = {
strokeWidth: true
}
const normalizeValues = (property, value) => {
const normalizeValue = (property, value) => {
if (!unitlessNumbers[property] && typeof value === 'number') {
value = `${value}px`
}
return value
}
export default normalizeValues
module.exports = normalizeValue

View File

@@ -2,23 +2,23 @@
* Reset unwanted styles beyond the control of React inline styles
*/
export const resetCSS =
`/* React Native Web */
html {font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}
`/* React Native for Web */
html {font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}
body {margin:0}
button::-moz-focus-inner, input::-moz-focus-inner {border:0;padding:0}
input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration {-webkit-appearance:none}`
input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration {display:none}`
/**
* Custom pointer event styles
*/
export const predefinedCSS =
`/* pointer-events */
._s_pe-a, ._s_pe-bo, ._s_pe-bn * {pointer-events:auto}
._s_pe-n, ._s_pe-bo *, ._s_pe-bn {pointer-events:none}`
.__style_pea, .__style_pebo, .__style_pebn * {pointer-events:auto}
.__style_pen, .__style_pebo *, .__style_pebn {pointer-events:none}`
export const predefinedClassNames = {
'pointerEvents:auto': '_s_pe-a',
'pointerEvents:box-none': '_s_pe-bn',
'pointerEvents:box-only': '_s_pe-bo',
'pointerEvents:none': '_s_pe-n'
'pointerEvents:auto': '__style_pea',
'pointerEvents:box-none': '__style_pebn',
'pointerEvents:box-only': '__style_pebo',
'pointerEvents:none': '__style_pen'
}

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