mirror of
https://github.com/zhigang1992/react-native-web.git
synced 2026-03-30 23:23:35 +08:00
Compare commits
186 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71cfd23624 | ||
|
|
77b8e4a1fc | ||
|
|
9543a79c3f | ||
|
|
e3eea6e132 | ||
|
|
4d3418a968 | ||
|
|
ea9bc734f1 | ||
|
|
e03af435ac | ||
|
|
97c0a31ce6 | ||
|
|
25d11ded46 | ||
|
|
6a73d77030 | ||
|
|
0b63ba4e89 | ||
|
|
51109d0768 | ||
|
|
ac04ecd69e | ||
|
|
1a670ba6a7 | ||
|
|
7a16d5711c | ||
|
|
9dde70fff5 | ||
|
|
203980ab66 | ||
|
|
924dc36d4a | ||
|
|
9b2421cdfa | ||
|
|
36ea662402 | ||
|
|
69962ae815 | ||
|
|
62d1a0f83d | ||
|
|
910286303a | ||
|
|
706fa887e6 | ||
|
|
c589d79035 | ||
|
|
83e4c68461 | ||
|
|
54597edbaf | ||
|
|
fc31287566 | ||
|
|
21cc8f47ba | ||
|
|
bf7beb4102 | ||
|
|
127d103c0a | ||
|
|
ae6132af56 | ||
|
|
3c4d7655db | ||
|
|
190966f411 | ||
|
|
8d5ecb84d5 | ||
|
|
b4a9177ce3 | ||
|
|
ad4a6c5be7 | ||
|
|
5f795dfc6c | ||
|
|
949cb75894 | ||
|
|
2e1914080f | ||
|
|
49e9e0ab5b | ||
|
|
ee4c544957 | ||
|
|
56549cf794 | ||
|
|
e6811b2134 | ||
|
|
d8b7dcc60f | ||
|
|
62a08f09ab | ||
|
|
3e7cd1a001 | ||
|
|
8441755d61 | ||
|
|
ba9fa2a7a0 | ||
|
|
e26edfb9ea | ||
|
|
9a8a9ad209 | ||
|
|
efccbe41bb | ||
|
|
f6f8d30aba | ||
|
|
6d7d98c149 | ||
|
|
77d201988d | ||
|
|
b3d7332ddd | ||
|
|
651d519500 | ||
|
|
06d8614519 | ||
|
|
3eced7e842 | ||
|
|
e627e0cd77 | ||
|
|
7ab33727c4 | ||
|
|
7295a8fee8 | ||
|
|
9a09456532 | ||
|
|
4cd38552cd | ||
|
|
b0f35f6c66 | ||
|
|
2dff45b561 | ||
|
|
fd9232201d | ||
|
|
29f6bd363c | ||
|
|
4845de5cb5 | ||
|
|
267a9b55bf | ||
|
|
7add5c524a | ||
|
|
8e0d94e092 | ||
|
|
25f96ba8ae | ||
|
|
2b90bd736f | ||
|
|
791ede06dd | ||
|
|
0567232942 | ||
|
|
e5ecc26d21 | ||
|
|
715c71b215 | ||
|
|
f8554ecc1e | ||
|
|
3292ced765 | ||
|
|
1c7fb4cb45 | ||
|
|
60409bea18 | ||
|
|
5c74c0efb7 | ||
|
|
a0187f9b1a | ||
|
|
74ef265d83 | ||
|
|
97b3a91c0e | ||
|
|
c65aa8a943 | ||
|
|
0aa60a3c29 | ||
|
|
8ac84f6da5 | ||
|
|
69166b1502 | ||
|
|
cc10de43eb | ||
|
|
c850a5fa8c | ||
|
|
1efe5a533f | ||
|
|
804132ce36 | ||
|
|
5335bcfd48 | ||
|
|
c0e7afc495 | ||
|
|
fa88548c3c | ||
|
|
39b2b2f979 | ||
|
|
fd04d65b03 | ||
|
|
0ab984f507 | ||
|
|
3d1ad50a58 | ||
|
|
92554321df | ||
|
|
1c9270c4ea | ||
|
|
8a5f9cd7d9 | ||
|
|
aac6b796b2 | ||
|
|
c77ce19f1b | ||
|
|
25b74d30c4 | ||
|
|
4191d58694 | ||
|
|
11b861ae64 | ||
|
|
68bf08112a | ||
|
|
b277b3e509 | ||
|
|
c135dddbd1 | ||
|
|
ffc6368162 | ||
|
|
501c19fe9b | ||
|
|
e1da11fa1d | ||
|
|
b2a4d742a9 | ||
|
|
8b965fdfa0 | ||
|
|
8cfef85934 | ||
|
|
6db24e9358 | ||
|
|
13e36bee65 | ||
|
|
93e8e90a1a | ||
|
|
894fd0362d | ||
|
|
a1664927ce | ||
|
|
ae2abc578a | ||
|
|
5f7b3f5fef | ||
|
|
75f653818a | ||
|
|
2c52d41b75 | ||
|
|
83f749d983 | ||
|
|
bf5046415c | ||
|
|
885d4586a9 | ||
|
|
ea0a778ba3 | ||
|
|
0a7eda2505 | ||
|
|
35385e7b69 | ||
|
|
3fd29697c0 | ||
|
|
a26033be2d | ||
|
|
fdb4ee4aae | ||
|
|
08300f624f | ||
|
|
7f5a2807e2 | ||
|
|
292f045c52 | ||
|
|
a19b57df4d | ||
|
|
1c444569ae | ||
|
|
0b8c4b8746 | ||
|
|
6772233837 | ||
|
|
cd89f88d96 | ||
|
|
b59bdb17b2 | ||
|
|
2bfa579fe4 | ||
|
|
f4c6e33b2f | ||
|
|
39b273f9d8 | ||
|
|
ec9985a3b3 | ||
|
|
0aa29d8816 | ||
|
|
45777e0405 | ||
|
|
84e06564d4 | ||
|
|
1d1b633317 | ||
|
|
565ec2fee8 | ||
|
|
33082f988e | ||
|
|
a2fb65a79c | ||
|
|
e727193809 | ||
|
|
d6db206ec4 | ||
|
|
b3beea9bb3 | ||
|
|
ef4de789ae | ||
|
|
86037ab740 | ||
|
|
6c20646f10 | ||
|
|
b5aa68dc7a | ||
|
|
5668c40ee6 | ||
|
|
be86250ac6 | ||
|
|
1e04dfc306 | ||
|
|
283ab2fa2e | ||
|
|
09dd9a224a | ||
|
|
c7524b7b6f | ||
|
|
5453c8843a | ||
|
|
e0f836ccb5 | ||
|
|
eada8e7fc7 | ||
|
|
114fb5f8c7 | ||
|
|
4aa87c79fa | ||
|
|
9107fd3de9 | ||
|
|
c72173ff88 | ||
|
|
edf0fda75a | ||
|
|
3b848fe378 | ||
|
|
7eff1a644e | ||
|
|
2750d70a93 | ||
|
|
65559f50e6 | ||
|
|
77fd21ea44 | ||
|
|
38e4de76cd | ||
|
|
0f42cd83e1 | ||
|
|
e6cbea82c4 | ||
|
|
6d4c9e881f |
11
.babelrc
11
.babelrc
@@ -1,7 +1,10 @@
|
||||
{
|
||||
"optional": [
|
||||
"es7.classProperties",
|
||||
"runtime"
|
||||
"presets": [
|
||||
"es2015",
|
||||
"stage-1",
|
||||
"react"
|
||||
],
|
||||
"stage": 1
|
||||
"plugins": [
|
||||
"transform-decorators-legacy"
|
||||
]
|
||||
}
|
||||
|
||||
9
.editorconfig
Normal file
9
.editorconfig
Normal 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
|
||||
@@ -1,2 +0,0 @@
|
||||
dist
|
||||
docs
|
||||
@@ -3,9 +3,6 @@
|
||||
"parser": "babel-eslint",
|
||||
// based on https://github.com/feross/standard
|
||||
"extends": [ "standard", "standard-react" ],
|
||||
"env": {
|
||||
"mocha": true
|
||||
},
|
||||
"rules": {
|
||||
// overrides of the standard style
|
||||
"space-before-function-paren": [ 2, { "anonymous": "always", "named": "never" } ],
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "0.12"
|
||||
- "5"
|
||||
- "4"
|
||||
before_script:
|
||||
- export DISPLAY=:99.0
|
||||
- sh -e /etc/init.d/xvfb start
|
||||
script:
|
||||
- npm run lint
|
||||
- npm test
|
||||
|
||||
149
CONTRIBUTING.md
149
CONTRIBUTING.md
@@ -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** — 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** — try to reproduce it using the
|
||||
latest `master` or development branch in the repository.
|
||||
Fork, then clone the repo:
|
||||
|
||||
3. **Isolate the problem** — create a [reduced test
|
||||
case](http://css-tricks.com/reduced-test-cases/) and a live example.
|
||||
```
|
||||
git clone https://github.com/your-username/react-native-web.git
|
||||
```
|
||||
|
||||
A good bug report contains as much detail as possible. What is your
|
||||
environment? What steps will reproduce the issue? What browser(s) and OS
|
||||
experience the problem? What would you expect to be the outcome? All these
|
||||
details really help!
|
||||
Run the examples:
|
||||
|
||||
Example:
|
||||
```
|
||||
npm run examples
|
||||
```
|
||||
|
||||
> Short and descriptive example bug report title
|
||||
>
|
||||
> A summary of the issue and the browser/OS environment in which it occurs. If
|
||||
> suitable, include the steps required to reproduce the bug.
|
||||
>
|
||||
> 1. This is the first step
|
||||
> 2. This is the second step
|
||||
> 3. Further steps, etc.
|
||||
>
|
||||
> `<url>` - a link to the reduced test case
|
||||
>
|
||||
> Any other information you want to share that is relevant to the issue being
|
||||
> reported. This might include the lines of code that you have identified as
|
||||
> causing the bug, and potential solutions (and your opinions on their
|
||||
> merits).
|
||||
### Building
|
||||
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
<a name="features"></a>
|
||||
## Feature requests
|
||||
To create a UMD build:
|
||||
|
||||
Feature requests are welcome. But take a moment to find out whether your idea
|
||||
fits with the scope and aims of the project. It's up to *you* to make a strong
|
||||
case to convince the project's developers of the merits of this feature. Please
|
||||
provide as much detail and context as possible.
|
||||
```
|
||||
npm run build:umd
|
||||
```
|
||||
|
||||
### Testing and Linting
|
||||
|
||||
<a name="pull-requests"></a>
|
||||
## Pull requests
|
||||
To run the tests:
|
||||
|
||||
Good pull requests - patches, improvements, new features - are a fantastic
|
||||
help. Please keep them focused in scope and avoid containing unrelated commits.
|
||||
```
|
||||
npm run test
|
||||
```
|
||||
|
||||
**Please ask first** before embarking on any significant pull request (e.g.
|
||||
implementing new features or components, refactoring code), otherwise you risk
|
||||
spending a lot of time working on something that the project's developers might
|
||||
not want to merge into the project.
|
||||
To continuously watch and run tests, run the following:
|
||||
|
||||
Development commands:
|
||||
```
|
||||
npm run test:watch
|
||||
```
|
||||
|
||||
* `npm start` – start the dev server and develop against live examples
|
||||
* `npm run lint` – run the linter
|
||||
* `npm run specs` – run the unit tests
|
||||
* `npm run build` – generate a build
|
||||
To perform linting, run the following:
|
||||
|
||||
Please follow this process for submitting a patch:
|
||||
```
|
||||
npm run lint
|
||||
```
|
||||
|
||||
1. [Fork](http://help.github.com/fork-a-repo/) the project, clone your fork,
|
||||
and configure the remotes:
|
||||
### New Features
|
||||
|
||||
```bash
|
||||
# Clone your fork of the repo into the current directory
|
||||
git clone https://github.com/<your-username>/react-native-web
|
||||
# Navigate to the newly cloned directory
|
||||
cd react-native-web
|
||||
# Assign the original repo to a remote called "upstream"
|
||||
git remote add upstream https://github.com/necolas/react-native-web
|
||||
```
|
||||
Please open an issue with a proposal for a new feature or refactoring before
|
||||
starting on the work. We don't want you to waste your efforts on a pull request
|
||||
that we won't want to accept.
|
||||
|
||||
2. If you cloned a while ago, get the latest changes from upstream:
|
||||
## Submitting Changes
|
||||
|
||||
```bash
|
||||
git checkout master
|
||||
git pull upstream master
|
||||
```
|
||||
* Open a new issue in the [Issue tracker](https://github.com/necolas/react-native-web/issues).
|
||||
* Fork the repo.
|
||||
* Create a new feature branch based off the `master` branch.
|
||||
* Make sure all tests pass and there are no linting errors.
|
||||
* Submit a pull request, referencing any issues it addresses.
|
||||
|
||||
3. Create a new topic branch (off the main project development branch) to
|
||||
contain your feature, change, or fix:
|
||||
Please try to keep your pull request focused in scope and avoid including unrelated commits.
|
||||
|
||||
```bash
|
||||
git checkout -b <topic-branch-name>
|
||||
```
|
||||
After you have submitted your pull request, we'll try to get back to you as soon as possible. We may suggest some changes or improvements.
|
||||
|
||||
4. Commit your changes in logical chunks. Please adhere to these [git commit
|
||||
message guidelines](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
|
||||
or your code is unlikely be merged into the main project. Use Git's
|
||||
[interactive rebase](https://help.github.com/articles/interactive-rebase)
|
||||
feature to tidy up your commits before making them public.
|
||||
|
||||
5. Locally merge (or rebase) the upstream development branch into your topic branch:
|
||||
|
||||
```bash
|
||||
git pull [--rebase] upstream master
|
||||
```
|
||||
|
||||
6. Push your topic branch up to your fork:
|
||||
|
||||
```bash
|
||||
git push origin <topic-branch-name>
|
||||
```
|
||||
|
||||
7. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/)
|
||||
with a clear title and description.
|
||||
|
||||
**IMPORTANT**: By submitting a patch, you agree to allow the project owner to
|
||||
license your work under the same license as that used by the project.
|
||||
Thank you for contributing!
|
||||
|
||||
21
LICENCE
21
LICENCE
@@ -1,21 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Nicolas Gallagher
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
31
LICENSE
Normal file
31
LICENSE
Normal file
@@ -0,0 +1,31 @@
|
||||
BSD License
|
||||
|
||||
For React Native software
|
||||
|
||||
Copyright (c) 2015-present, Nicolas Gallagher. All rights reserved.
|
||||
Copyright (c) 2015-present, Facebook, Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name Facebook nor the names of its contributors may be used to
|
||||
endorse or promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
244
README.md
244
README.md
@@ -2,188 +2,128 @@
|
||||
|
||||
[![Build Status][travis-image]][travis-url]
|
||||
[![npm version][npm-image]][npm-url]
|
||||

|
||||
|
||||
The core [React Native][react-native-url] components adapted and expanded upon
|
||||
for the web, backed by a precomputed CSS library. ~19KB minified and gzipped.
|
||||
[React Native][react-native-url] components and APIs for the Web.
|
||||
|
||||
## Table of contents
|
||||
Browser support: Chrome, Firefox, Safari >= 7, IE 10, Edge.
|
||||
|
||||
* [Install](#install)
|
||||
* [Use](#use)
|
||||
* [Components](#components)
|
||||
* [Styling](#styling)
|
||||
* [Contributing](#contributing)
|
||||
* [Thanks](#thanks)
|
||||
* [License](#license)
|
||||
## Overview
|
||||
|
||||
## Install
|
||||
"React Native for Web" is a project to bring React Native's building blocks and
|
||||
touch handling to the Web.
|
||||
|
||||
React Native provides a foundational layer to support interoperable,
|
||||
zero-configuration React component development. This is missing from React's
|
||||
web ecosystem where OSS components rely on inline styles (usually without
|
||||
vendor prefixes), or require build tool configuration. This project allows
|
||||
components built upon React Native to be run on the Web, and it manages all
|
||||
component styling out-of-the-box.
|
||||
|
||||
For example, the [`View`](docs/apis/View.md) component makes it easy to build
|
||||
cross-browser layouts with flexbox, such as stacked and nested boxes with
|
||||
margin and padding. And the [`StyleSheet`](docs/guides/style.md) API converts
|
||||
styles defined in JavaScript into "Atomic CSS".
|
||||
|
||||
## Quick start
|
||||
|
||||
To install in your app:
|
||||
|
||||
```
|
||||
npm install --save react react-native-web
|
||||
npm install --save react@0.14 react-dom@0.14 react-native-web
|
||||
```
|
||||
|
||||
## Use
|
||||
Read the [Client and Server rendering](docs/guides/rendering.md) guide.
|
||||
|
||||
React Native for Web exports its components and a reference to the `React`
|
||||
installation. Styles are authored in JavaScript as plain objects.
|
||||
## Examples
|
||||
|
||||
Demos:
|
||||
|
||||
* [React Native for Web: Playground](http://codepen.io/necolas/pen/PZzwBR).
|
||||
* [TicTacToe](http://codepen.io/necolas/full/eJaLZd/)
|
||||
* [2048](http://codepen.io/necolas/full/wMVvxj/)
|
||||
|
||||
Sample:
|
||||
|
||||
```js
|
||||
import React, { View } from 'react-native-web'
|
||||
import React, { AppRegistry, Image, StyleSheet, Text, View } from 'react-native'
|
||||
|
||||
class MyComponent extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.root} />
|
||||
)
|
||||
}
|
||||
}
|
||||
// Components
|
||||
const Card = ({ children }) => <View style={styles.card}>{children}</View>
|
||||
const Title = ({ children }) => <Text style={styles.title}>{children}</Text>
|
||||
const Photo = ({ uri }) => <Image source={{ uri }} style={styles.image} />
|
||||
const App = () => (
|
||||
<Card>
|
||||
<Title>App Card</Title>
|
||||
<Photo uri="/some-photo.jpg" />
|
||||
</Card>
|
||||
)
|
||||
|
||||
const styles = {
|
||||
root: {
|
||||
borderColor: 'currentcolor'
|
||||
borderWidth: '5px',
|
||||
flexDirection: 'row'
|
||||
height: '5em'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Components
|
||||
|
||||
### [`Image`](docs/components/Image.md)
|
||||
|
||||
An accessibile image component with support for image resizing, default image,
|
||||
and child content.
|
||||
|
||||
### [`ListView`](docs/components/ListView.md)
|
||||
|
||||
(TODO)
|
||||
|
||||
### [`ScrollView`](docs/components/ListView.md)
|
||||
|
||||
(TODO)
|
||||
|
||||
### [`Swipeable`](docs/components/Swipeable.md)
|
||||
|
||||
Touch bindings for swipe gestures.
|
||||
|
||||
### [`Text`](docs/components/Text.md)
|
||||
|
||||
Displays text as an inline block and supports basic press handling.
|
||||
|
||||
### [`TextInput`](docs/components/TextInput.md)
|
||||
|
||||
Accessible single- and multi-line text input via a keyboard. Supports features
|
||||
|
||||
### [`Touchable`](docs/components/Touchable.md)
|
||||
|
||||
Touch bindings for press and long press.
|
||||
|
||||
### [`View`](docs/components/View.md)
|
||||
|
||||
The fundamental UI building block: layout with flexbox, layout and positioning
|
||||
styles, and accessibility controls.
|
||||
|
||||
## Styling
|
||||
|
||||
React Native for Web provides a mechanism to declare all your styles and layout
|
||||
inline with the components that use them. The `View` component makes it easy to
|
||||
build common layouts with flexbox, such as stacked and nested boxes with margin
|
||||
and padding.
|
||||
|
||||
Styling is identical to using inline styles in React, but most inline styles
|
||||
are converted to single-purpose classes. The current implementation includes
|
||||
300+ precomputed CSS declarations (~4.5KB gzipped) that cover a large
|
||||
proportion of common styles. A more sophisticated build-time implementation may
|
||||
produce a slightly larger CSS file for large apps, and fall back to fewer
|
||||
inline styles. Read more about the [styling
|
||||
strategy](docs/style.md).
|
||||
|
||||
See this [guide to flexbox][flexbox-guide-url].
|
||||
|
||||
```js
|
||||
import React, { Image, Text, View } from 'react-native-web'
|
||||
|
||||
class App extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.row}>
|
||||
<Image
|
||||
source={{ uri: 'http://facebook.github.io/react/img/logo_og.png' }}
|
||||
style={styles.image}
|
||||
/>
|
||||
<View style={styles.text}>
|
||||
<Text style={styles.title}>
|
||||
React Native Web
|
||||
</Text>
|
||||
<Text style={styles.subtitle}>
|
||||
Build high quality web apps using React
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
const styles = {
|
||||
row: {
|
||||
flexDirection: 'row',
|
||||
margin: 40
|
||||
},
|
||||
image: {
|
||||
height: 40,
|
||||
marginRight: 10,
|
||||
width: 40,
|
||||
},
|
||||
text: {
|
||||
flex: 1,
|
||||
// Styles
|
||||
const styles = StyleSheet.create({
|
||||
card: {
|
||||
flexGrow: 1,
|
||||
justifyContent: 'center'
|
||||
},
|
||||
title: {
|
||||
fontSize: '1.25rem',
|
||||
fontWeight: 'bold'
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: '1rem'
|
||||
image: {
|
||||
height: 40,
|
||||
marginVertical: 10,
|
||||
width: 40
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// App registration and rendering
|
||||
AppRegistry.registerComponent('MyApp', () => App)
|
||||
AppRegistry.runApplication('MyApp', { rootTag: document.getElementById('react-root') })
|
||||
```
|
||||
|
||||
Combine and override style objects:
|
||||
## Documentation
|
||||
|
||||
```js
|
||||
import baseStyle from './baseStyle'
|
||||
Guides:
|
||||
|
||||
const buttonStyle = {
|
||||
...baseStyle,
|
||||
backgroundColor: '#333',
|
||||
color: '#fff'
|
||||
}
|
||||
```
|
||||
* [Accessibility](docs/guides/accessibility.md)
|
||||
* [Client and server rendering](docs/guides/rendering.md)
|
||||
* [Direct manipulation](docs/guides/direct-manipulation.md)
|
||||
* [Known issues](docs/guides/known-issues.md)
|
||||
* [React Native](docs/guides/react-native.md)
|
||||
* [Style](docs/guides/style.md)
|
||||
|
||||
## Contributing
|
||||
Exported modules:
|
||||
|
||||
Please read the [contribution guidelines][contributing-url]. Contributions are
|
||||
welcome!
|
||||
|
||||
## Thanks
|
||||
|
||||
Thanks to current and past members of the React and React Native teams (in
|
||||
particular Vjeux and Pete Hunt), and Tobias Koppers for Webpack and CSS loader.
|
||||
|
||||
Thanks to [react-swipeable](https://github.com/dogfessional/react-swipeable/)
|
||||
for the current implementation of `Swipeable`, and to
|
||||
[react-tappable](https://github.com/JedWatson/react-tappable) for backing the
|
||||
current implementation of `Touchable`.
|
||||
* Components
|
||||
* [`ActivityIndicator`](docs/components/ActivityIndicator.md)
|
||||
* [`Image`](docs/components/Image.md)
|
||||
* [`ListView`](docs/components/ListView.md)
|
||||
* [`Portal`](docs/components/Portal.md)
|
||||
* [`ScrollView`](docs/components/ScrollView.md)
|
||||
* [`Text`](docs/components/Text.md)
|
||||
* [`TextInput`](docs/components/TextInput.md)
|
||||
* [`TouchableHighlight`](http://facebook.github.io/react-native/releases/0.22/docs/touchablehighlight.html) (mirrors React Native)
|
||||
* [`TouchableOpacity`](http://facebook.github.io/react-native/releases/0.22/docs/touchableopacity.html) (mirrors React Native)
|
||||
* [`TouchableWithoutFeedback`](docs/components/TouchableWithoutFeedback.md)
|
||||
* [`View`](docs/components/View.md)
|
||||
* APIs
|
||||
* [`Animated`](http://facebook.github.io/react-native/releases/0.20/docs/animated.html) (mirrors React Native)
|
||||
* [`AppRegistry`](docs/apis/AppRegistry.md)
|
||||
* [`AppState`](docs/apis/AppState.md)
|
||||
* [`AsyncStorage`](docs/apis/AsyncStorage.md)
|
||||
* [`Dimensions`](docs/apis/Dimensions.md)
|
||||
* [`NativeMethods`](docs/apis/NativeMethods.md)
|
||||
* [`NetInfo`](docs/apis/NetInfo.md)
|
||||
* [`PanResponder`](http://facebook.github.io/react-native/releases/0.20/docs/panresponder.html#content) (mirrors React Native)
|
||||
* [`PixelRatio`](docs/apis/PixelRatio.md)
|
||||
* [`Platform`](docs/apis/Platform.md)
|
||||
* [`StyleSheet`](docs/apis/StyleSheet.md)
|
||||
|
||||
## License
|
||||
|
||||
Copyright (c) 2015 Nicolas Gallagher. Released under the [MIT
|
||||
license](http://www.opensource.org/licenses/mit-license.php).
|
||||
React Native for Web is [BSD licensed](LICENSE).
|
||||
|
||||
[contributing-url]: https://github.com/necolas/react-native-web/blob/master/CONTRIBUTING.md
|
||||
[flexbox-guide-url]: https://css-tricks.com/snippets/css/a-guide-to-flexbox/
|
||||
[npm-image]: https://img.shields.io/npm/v/react-native-web.svg
|
||||
[npm-image]: https://badge.fury.io/js/react-native-web.svg
|
||||
[npm-url]: https://npmjs.org/package/react-native-web
|
||||
[react-native-url]: https://facebook.github.io/react-native/
|
||||
[travis-image]: https://travis-ci.org/necolas/react-native-web.svg?branch=master
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
var assign = require('object-assign')
|
||||
var path = require('path')
|
||||
var webpackConfig = require('./webpack-base.config.js')
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
basePath: path.resolve(__dirname, '..'),
|
||||
browsers: [ process.env.TRAVIS ? 'Firefox' : 'Chrome' ],
|
||||
browserNoActivityTimeout: 60000,
|
||||
client: {
|
||||
captureConsole: true,
|
||||
mocha: {
|
||||
ui: 'tdd'
|
||||
},
|
||||
useIframe: true
|
||||
},
|
||||
files: [
|
||||
'src/specs.bundle.js'
|
||||
],
|
||||
frameworks: [
|
||||
'mocha'
|
||||
],
|
||||
plugins: [
|
||||
'karma-chrome-launcher',
|
||||
'karma-firefox-launcher',
|
||||
'karma-mocha',
|
||||
'karma-sourcemap-loader',
|
||||
'karma-webpack'
|
||||
],
|
||||
preprocessors: {
|
||||
'src/specs.bundle.js': [ 'webpack', 'sourcemap' ]
|
||||
},
|
||||
reporters: [ 'dots' ],
|
||||
singleRun: true,
|
||||
webpack: assign({}, webpackConfig, { devtool: 'inline' }),
|
||||
webpackMiddleware: {
|
||||
stats: {
|
||||
assetsSort: 'name',
|
||||
colors: true,
|
||||
children: false,
|
||||
chunks: false,
|
||||
modules: false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
var autoprefixer = require('autoprefixer-core')
|
||||
|
||||
module.exports = {
|
||||
module: {
|
||||
loaders: [
|
||||
{
|
||||
test: /\.css$/,
|
||||
loader: [
|
||||
'style-loader',
|
||||
'css-loader?module&localIdentName=[hash:base64:5]',
|
||||
'!postcss-loader'
|
||||
].join('!')
|
||||
},
|
||||
{
|
||||
test: /\.jsx?$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'babel-loader',
|
||||
query: { cacheDirectory: true }
|
||||
}
|
||||
]
|
||||
},
|
||||
postcss: [ autoprefixer ]
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
var assign = require('object-assign')
|
||||
var base = require('./webpack-base.config.js')
|
||||
var webpack = require('webpack')
|
||||
var UglifyJsPlugin = webpack.optimize.UglifyJsPlugin
|
||||
|
||||
var plugins = []
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
plugins.push(
|
||||
new UglifyJsPlugin({
|
||||
compress: {
|
||||
dead_code: true,
|
||||
drop_console: true,
|
||||
screw_ie8: true,
|
||||
warnings: true
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
module.exports = assign({}, base, {
|
||||
entry: {
|
||||
main: './src/index'
|
||||
},
|
||||
externals: [{
|
||||
react: true
|
||||
}],
|
||||
output: {
|
||||
filename: 'react-native-web.js',
|
||||
library: 'ReactNativeWeb',
|
||||
libraryTarget: 'commonjs2',
|
||||
path: './dist'
|
||||
},
|
||||
plugins: plugins
|
||||
})
|
||||
63
docs/apis/AppRegistry.md
Normal file
63
docs/apis/AppRegistry.md
Normal 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
61
docs/apis/AppState.md
Normal 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
71
docs/apis/AsyncStorage.md
Normal 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
13
docs/apis/Dimensions.md
Normal 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')`
|
||||
42
docs/apis/NativeMethods.md
Normal file
42
docs/apis/NativeMethods.md
Normal 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
77
docs/apis/NetInfo.md
Normal 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
51
docs/apis/PixelRatio.md
Normal 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
28
docs/apis/Platform.md
Normal 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!');
|
||||
}
|
||||
```
|
||||
69
docs/apis/StyleSheet.md
Normal file
69
docs/apis/StyleSheet.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# StyleSheet
|
||||
|
||||
The `StyleSheet` abstraction converts predefined styles to (vendor-prefixed)
|
||||
CSS without requiring a compile-time step. Some styles cannot be resolved
|
||||
outside of the render loop and are applied as inline styles. Read more about to
|
||||
[how style your application](docs/guides/style).
|
||||
|
||||
## Methods
|
||||
|
||||
**create**(obj: {[key: string]: any})
|
||||
|
||||
Each key of the object passed to `create` must define a style object.
|
||||
|
||||
**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({
|
||||
container: {
|
||||
borderRadius: 4,
|
||||
borderWidth: 0.5,
|
||||
borderColor: '#d6d7da',
|
||||
},
|
||||
title: {
|
||||
fontSize: 19,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
activeTitle: {
|
||||
color: 'red',
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Use styles:
|
||||
|
||||
```js
|
||||
<View style={styles.container}>
|
||||
<Text
|
||||
style={[
|
||||
styles.title,
|
||||
this.props.isActive && styles.activeTitle
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
```
|
||||
|
||||
Or:
|
||||
|
||||
```js
|
||||
<View style={styles.container}>
|
||||
<Text
|
||||
style={{
|
||||
...styles.title,
|
||||
...(this.props.isActive && styles.activeTitle)
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
```
|
||||
69
docs/components/ActivityIndicator.md
Normal file
69
docs/components/ActivityIndicator.md
Normal 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'
|
||||
}
|
||||
})
|
||||
```
|
||||
@@ -3,75 +3,87 @@
|
||||
An accessibile image component with support for image resizing, default image,
|
||||
and child content.
|
||||
|
||||
Unsupported React Native props:
|
||||
`capInsets` (ios),
|
||||
`onProgress` (ios)
|
||||
|
||||
## Props
|
||||
|
||||
**accessibilityLabel** string
|
||||
**accessibilityLabel**: string
|
||||
|
||||
The text that's read by the screen reader when the user interacts with the image.
|
||||
The text that's read by a screenreader when someone interacts with the image.
|
||||
|
||||
**children** any
|
||||
**accessible**: bool
|
||||
|
||||
When `false`, the view is hidden from screenreaders. Default: `true`.
|
||||
|
||||
**children**: any
|
||||
|
||||
Content to display over the image.
|
||||
|
||||
**defaultSource** { uri: string }
|
||||
**defaultSource**: { uri: string }
|
||||
|
||||
An image to display as a placeholder while downloading the final image off the network.
|
||||
|
||||
**onError** function
|
||||
**onError**: function
|
||||
|
||||
Invoked on load error with `{nativeEvent: {error}}`.
|
||||
|
||||
**onLoad** function
|
||||
**onLayout**: function
|
||||
|
||||
TODO
|
||||
|
||||
**onLoad**: function
|
||||
|
||||
Invoked when load completes successfully.
|
||||
|
||||
**onLoadEnd** function
|
||||
**onLoadEnd**: function
|
||||
|
||||
Invoked when load either succeeds or fails,
|
||||
|
||||
**onLoadStart** function
|
||||
**onLoadStart**: function
|
||||
|
||||
Invoked on load start.
|
||||
|
||||
**resizeMode** oneOf('clip', 'contain', 'cover', 'stretch')
|
||||
**resizeMode**: oneOf('contain', 'cover', 'none', 'stretch') = 'stretch'
|
||||
|
||||
Determines how to resize the image when the frame doesn't match the raw image
|
||||
dimensions. Default: `cover`.
|
||||
dimensions.
|
||||
|
||||
**source** { uri: string }
|
||||
**source**: { uri: string }
|
||||
|
||||
`uri` is a string representing the resource identifier for the image, which
|
||||
could be an http address or a base64 encoded image.
|
||||
|
||||
**style** style
|
||||
**style**: style
|
||||
|
||||
[View](View.md) style
|
||||
+ ...[View#style](View.md)
|
||||
+ `resizeMode`
|
||||
|
||||
Defaults:
|
||||
|
||||
```js
|
||||
{
|
||||
alignSelf: 'flex-start',
|
||||
backgroundColor: 'lightGray'
|
||||
}
|
||||
```
|
||||
|
||||
**testID** string
|
||||
**testID**: string
|
||||
|
||||
Used to locate a view in end-to-end tests.
|
||||
|
||||
## Properties
|
||||
|
||||
static **resizeMode**: Object
|
||||
|
||||
Example usage:
|
||||
|
||||
```
|
||||
<Image resizeMode={Image.resizeMode.contain} />
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
```js
|
||||
import placeholderAvatar from './placeholderAvatar.png'
|
||||
import React, { Image } from 'react-native-web'
|
||||
import React, { Component, Image, PropTypes, StyleSheet } from 'react-native'
|
||||
|
||||
const { Component, PropTypes } = React;
|
||||
|
||||
class Avatar extends Component {
|
||||
export default class ImageExample extends Component {
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
this.state = { isLoaded: false }
|
||||
this.state = { loading: true }
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
@@ -86,12 +98,12 @@ class Avatar extends Component {
|
||||
|
||||
_onLoad(e) {
|
||||
console.log('Avatar.onLoad', e)
|
||||
this.setState({ isLoaded: true })
|
||||
this.setState({ loading: false })
|
||||
}
|
||||
|
||||
render() {
|
||||
const { size, testID, user } = this.props
|
||||
const { isLoaded } = this.state
|
||||
const loadingStyle = this.state.loading ? { styles.loading } : { }
|
||||
|
||||
return (
|
||||
<Image
|
||||
@@ -100,33 +112,36 @@ class Avatar extends Component {
|
||||
onLoad={this._onLoad.bind(this)}
|
||||
resizeMode='cover'
|
||||
source={{ uri: user.avatarUrl }}
|
||||
style={ ...style.base, ...style[size], ...style[isLoaded] }
|
||||
style={[
|
||||
styles.base,
|
||||
styles[size],
|
||||
loadingStyle
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const style = {
|
||||
const styles = StyleSheet.create({
|
||||
base: {
|
||||
borderColor: 'white',
|
||||
borderRadius: '5px',
|
||||
borderWidth: '5px',
|
||||
opacity: 0.5,
|
||||
borderRadius: 5,
|
||||
borderWidth: 5
|
||||
},
|
||||
loading: {
|
||||
opacity: 0.5
|
||||
},
|
||||
small: {
|
||||
height: '32px',
|
||||
width: '32px'
|
||||
height: 32,
|
||||
width: 32
|
||||
},
|
||||
normal: {
|
||||
height: '48px',
|
||||
width: '48px'
|
||||
height: 48,
|
||||
width: 48
|
||||
},
|
||||
large: {
|
||||
height: '64px',
|
||||
width: '64px'
|
||||
height: 64,
|
||||
width: 64
|
||||
}
|
||||
isLoaded: {
|
||||
opacity: 1
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
@@ -1,36 +1,26 @@
|
||||
# ListView
|
||||
|
||||
TODO
|
||||
|
||||
## Props
|
||||
|
||||
**children** any
|
||||
**children**: any
|
||||
|
||||
Content to display over the image.
|
||||
|
||||
**style** style
|
||||
|
||||
+ `property` type
|
||||
|
||||
Defaults:
|
||||
|
||||
```js
|
||||
{
|
||||
}
|
||||
```
|
||||
**style**: style
|
||||
|
||||
+ ...[View#style](View.md)
|
||||
|
||||
## Examples
|
||||
|
||||
```js
|
||||
import React, { ListView } from 'react-native-web'
|
||||
import React, { Component, ListView, PropTypes } from 'react-native'
|
||||
|
||||
const { Component, PropTypes } = React;
|
||||
export default class ListViewExample extends Component {
|
||||
static propTypes = {}
|
||||
|
||||
class Example extends Component {
|
||||
static propTypes = {
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
}
|
||||
static defaultProps = {}
|
||||
|
||||
render() {
|
||||
return (
|
||||
|
||||
67
docs/components/Portal.md
Normal file
67
docs/components/Portal.md
Normal 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>
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1,40 +1,123 @@
|
||||
# ScrollView
|
||||
|
||||
A scrollable `View` that provides itegration with the touch-locking "responder"
|
||||
system. `ScrollView`'s must have a bounded height: either set the height of the
|
||||
view directly (discouraged) or make sure all parent views have bounded height
|
||||
(e.g., transfer `{ flex: 1}` down the view stack).
|
||||
|
||||
## Props
|
||||
|
||||
**children** any
|
||||
[...View props](./View.md)
|
||||
|
||||
Content to display over the image.
|
||||
**contentContainerStyle**: style
|
||||
|
||||
**style** style
|
||||
These styles will be applied to the scroll view content container which wraps
|
||||
all of the child views.
|
||||
|
||||
+ `property` type
|
||||
**horizontal**: bool = false
|
||||
|
||||
Defaults:
|
||||
When true, the scroll view's children are arranged horizontally in a row
|
||||
instead of vertically in a column.
|
||||
|
||||
```js
|
||||
{
|
||||
}
|
||||
```
|
||||
**keyboardDismissMode**: oneOf('none', 'on-drag') = 'none'
|
||||
|
||||
Determines whether the keyboard gets dismissed in response to a scroll drag.
|
||||
|
||||
* `none` (the default), drags do not dismiss the keyboard.
|
||||
* `on-drag`, the keyboard is dismissed when a drag begins.
|
||||
* `interactive` (not supported on web; same as `none`)
|
||||
|
||||
**onContentSizeChange**: function
|
||||
|
||||
TODO
|
||||
|
||||
Called when scrollable content view of the `ScrollView` changes. It's
|
||||
implemented using the `onLayout` handler attached to the content container
|
||||
which this `ScrollView` renders.
|
||||
|
||||
**onScroll**: function
|
||||
|
||||
Fires at most once per frame during scrolling. The frequency of the events can
|
||||
be contolled using the `scrollEventThrottle` prop.
|
||||
|
||||
**refreshControl**: element
|
||||
|
||||
TODO
|
||||
|
||||
A [RefreshControl](../RefreshControl) component, used to provide
|
||||
pull-to-refresh functionality for the `ScrollView`.
|
||||
|
||||
**scrollEnabled**: bool = true
|
||||
|
||||
When false, the content does not scroll.
|
||||
|
||||
**scrollEventThrottle**: number = 0
|
||||
|
||||
This controls how often the scroll event will be fired while scrolling (in
|
||||
events per seconds). A higher number yields better accuracy for code that is
|
||||
tracking the scroll position, but can lead to scroll performance problems. The
|
||||
default value is `0`, which means the scroll event will be sent only once each
|
||||
time the view is scrolled.
|
||||
|
||||
## Instance methods
|
||||
|
||||
**getInnerViewNode()**: any
|
||||
|
||||
Returns a reference to the underlying content container DOM node within the `ScrollView`.
|
||||
|
||||
**getScrollableNode()**: any
|
||||
|
||||
Returns a reference to the underlying scrollable DOM node.
|
||||
|
||||
**getScrollResponder()**: Component
|
||||
|
||||
Returns a reference to the underlying scroll responder, which supports
|
||||
operations like `scrollTo`. All `ScrollView`-like components should implement
|
||||
this method so that they can be composed while providing access to the
|
||||
underlying scroll responder's methods.
|
||||
|
||||
**scrollTo(options: { x: number = 0; y: number = 0; animated: boolean = true })**
|
||||
|
||||
Scrolls to a given `x`, `y` offset (animation is not currently supported).
|
||||
|
||||
## Examples
|
||||
|
||||
```js
|
||||
import React, { ScrollView } from 'react-native-web'
|
||||
import React, { Component, ScrollView, StyleSheet } from 'react-native'
|
||||
import Item from './Item'
|
||||
|
||||
const { Component, PropTypes } = React;
|
||||
|
||||
class Example extends Component {
|
||||
static propTypes = {
|
||||
export default class ScrollViewExample extends Component {
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
this.state = {
|
||||
items: Array.from(new Array(20)).map((_, i) => ({ id: i }))
|
||||
}
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
onScroll(e) {
|
||||
console.log(e)
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<ScrollView
|
||||
children={this.state.items.map((item) => <Item {...item} />)}
|
||||
contentContainerStyle={styles.container}
|
||||
horizontal
|
||||
onScroll={(e) => this.onScroll(e)}
|
||||
scrollEventThrottle={60}
|
||||
style={styles.root}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
borderWidth: 1
|
||||
},
|
||||
container: {
|
||||
padding: 10
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
# Swipeable
|
||||
|
||||
## Props
|
||||
|
||||
**delta** number
|
||||
|
||||
Number of pixels that must be swiped before events are dispatched. Default: `10`.
|
||||
|
||||
**flickThreshold** number
|
||||
|
||||
The velocity threshold at which a swipe is considered a flick. Default: `0.6`.
|
||||
|
||||
**onSwiped** function
|
||||
|
||||
(SyntheticTouchEvent, deltaX, deltaY, isFlick) => swipeHandler
|
||||
|
||||
Called once a swipe has ended.
|
||||
|
||||
**onSwipedDown** function
|
||||
|
||||
(SyntheticTouchEvent, delta, isFlick) => swipeHandler
|
||||
|
||||
Called once a swipe-down has ended.
|
||||
|
||||
**onSwipedLeft** function
|
||||
|
||||
(SyntheticTouchEvent, delta, isFlick) => swipeHandler
|
||||
|
||||
Called once a swipe-left has ended.
|
||||
|
||||
**onSwipedUp** function
|
||||
|
||||
(SyntheticTouchEvent, delta, isFlick) => swipeHandler
|
||||
|
||||
Called once a swipe-up has ended.
|
||||
|
||||
**onSwipedRight** function
|
||||
|
||||
(SyntheticTouchEvent, delta, isFlick) => swipeHandler
|
||||
|
||||
Called once a swipe-right has ended.
|
||||
|
||||
**onSwipingDown** function
|
||||
|
||||
(SyntheticTouchEvent, delta, isFlick) => swipeHandler
|
||||
|
||||
Called while a swipe-down is in progress.
|
||||
|
||||
**onSwipingLeft** function
|
||||
|
||||
(SyntheticTouchEvent, delta, isFlick) => swipeHandler
|
||||
|
||||
Called while a swipe-left is in progress.
|
||||
|
||||
**onSwipingRight** function
|
||||
|
||||
(SyntheticTouchEvent, delta, isFlick) => swipeHandler
|
||||
|
||||
Called while a swipe-right is in progress.
|
||||
|
||||
**onSwipingUp** function
|
||||
|
||||
(SyntheticTouchEvent, delta, isFlick) => swipeHandler
|
||||
|
||||
Called while a swipe-up is in progress.
|
||||
|
||||
## Examples
|
||||
|
||||
```js
|
||||
import React, { Swipeable } from 'react-native-web'
|
||||
|
||||
const { Component, PropTypes } = React;
|
||||
|
||||
class Example extends Component {
|
||||
static propTypes = {
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
}
|
||||
|
||||
_onSwiped(event, x, y, isFlick) {
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Swipeable
|
||||
onSwiped={this._onSwiped.bind(this)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1,75 +1,96 @@
|
||||
# Text
|
||||
|
||||
`Text` is component for displaying text. It supports style, basic touch
|
||||
handling, and inherits typographic styles from ancestor elements. In a
|
||||
divergence from React Native, components other than `Text` can be children of a
|
||||
`Text` component.
|
||||
handling, and inherits typographic styles from ancestor elements.
|
||||
|
||||
The `Text` is unique relative to layout: child elements use text layout
|
||||
(`inline-block`) rather than flexbox layout. This means that elements inside of
|
||||
a `Text` are not rectangles, as they wrap when reaching the edge of their
|
||||
(`inline`) rather than flexbox layout. This means that elements inside of a
|
||||
`Text` are not rectangles, as they wrap when reaching the edge of their
|
||||
container.
|
||||
|
||||
Unsupported React Native props:
|
||||
`allowFontScaling` (ios),
|
||||
`suppressHighlighting` (ios)
|
||||
|
||||
## Props
|
||||
|
||||
**children** any
|
||||
NOTE: `Text` will transfer all other props to the rendered HTML element.
|
||||
|
||||
Child content
|
||||
(web) **accessibilityLabel**: string
|
||||
|
||||
**component** function, string
|
||||
Defines the text available to assistive technologies upon interaction with the
|
||||
element. (This is implemented using `aria-label`.)
|
||||
|
||||
Default is `span`.
|
||||
(web) **accessibilityRole**: oneOf(roles)
|
||||
|
||||
**numberOfLines** number
|
||||
Allows assistive technologies to present and support interaction with the view
|
||||
in a manner that is consistent with user expectations for similar views of that
|
||||
type. For example, marking a touchable view with an `accessibilityRole` of
|
||||
`button`. (This is implemented using [ARIA roles](http://www.w3.org/TR/wai-aria/roles#role_definitions)).
|
||||
|
||||
Truncates the text with an ellipsis after this many lines.
|
||||
Note: Avoid changing `accessibilityRole` values over time or after user
|
||||
actions. Generally, accessibility APIs do not provide a means of notifying
|
||||
assistive technologies of a `role` value change.
|
||||
|
||||
**onPress** function
|
||||
(web) **accessible**: bool = true
|
||||
|
||||
When `false`, the text is hidden from assistive technologies. (This is
|
||||
implemented using `aria-hidden`.)
|
||||
|
||||
**children**: any
|
||||
|
||||
Child content.
|
||||
|
||||
**numberOfLines**: number
|
||||
|
||||
Truncates the text with an ellipsis after this many lines. Currently only supports `1`.
|
||||
|
||||
**onPress**: function
|
||||
|
||||
This function is called on press.
|
||||
|
||||
**style** style
|
||||
**style**: style
|
||||
|
||||
+ `backgroundColor`
|
||||
+ ...[View#style](View.md)
|
||||
+ `color`
|
||||
+ `direction`
|
||||
+ `fontFamily`
|
||||
+ `fontSize`
|
||||
+ `fontStyle`
|
||||
+ `fontWeight`
|
||||
+ `letterSpacing`
|
||||
+ `lineHeight`
|
||||
+ `margin`
|
||||
+ `padding`
|
||||
+ `textAlign`
|
||||
+ `textDecoration`
|
||||
+ `textAlignVertical`
|
||||
+ `textDecorationLine`
|
||||
+ `textShadow`
|
||||
+ `textTransform`
|
||||
+ `whiteSpace`
|
||||
+ `wordWrap`
|
||||
+ `writingDirection`
|
||||
|
||||
**testID** string
|
||||
**testID**: string
|
||||
|
||||
Used to locate this view in end-to-end tests.
|
||||
|
||||
## Examples
|
||||
|
||||
```js
|
||||
import React, { Text } from 'react-native-web'
|
||||
import React, { Component, PropTypes, StyleSheet, Text } from 'react-native'
|
||||
|
||||
const { Component, PropTypes } = React
|
||||
|
||||
class PrettyText extends Component {
|
||||
export default class PrettyText extends Component {
|
||||
static propTypes = {
|
||||
...Text.propTypes,
|
||||
color: PropTypes.oneOf(['white', 'gray', 'red']),
|
||||
size: PropTypes.oneOf(['small', 'normal', 'large']),
|
||||
weight: PropTypes.oneOf(['light', 'normal', 'bold'])
|
||||
}
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
...Text.defaultProps,
|
||||
color: 'gray',
|
||||
size: 'normal',
|
||||
weight: 'normal'
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { color, size, style, weight, ...other } = this.props;
|
||||
@@ -77,32 +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 = {
|
||||
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' }
|
||||
})
|
||||
```
|
||||
|
||||
@@ -5,107 +5,207 @@ such as auto-complete, auto-focus, placeholder text, and event callbacks.
|
||||
|
||||
Note: some props are exclusive to or excluded from `multiline`.
|
||||
|
||||
Unsupported React Native props:
|
||||
`autoCapitalize`,
|
||||
`autoCorrect`,
|
||||
`onEndEditing`,
|
||||
`onSubmitEditing`,
|
||||
`clearButtonMode` (ios),
|
||||
`enablesReturnKeyAutomatically` (ios),
|
||||
`returnKeyType` (ios),
|
||||
`selectionState` (ios),
|
||||
`textAlign` (android),
|
||||
`textAlignVertical` (android),
|
||||
`underlineColorAndroid` (android)
|
||||
|
||||
## Props
|
||||
|
||||
**autoComplete** bool
|
||||
(web) **accessibilityLabel**: string
|
||||
|
||||
Defines the text label available to assistive technologies upon interaction
|
||||
with the element. (This is implemented using `aria-label`.)
|
||||
|
||||
(web) **autoComplete**: bool = false
|
||||
|
||||
Indicates whether the value of the control can be automatically completed by the browser.
|
||||
|
||||
**autoFocus** bool
|
||||
**autoFocus**: bool = false
|
||||
|
||||
If true, focuses the input on `componentDidMount`. Only the first form element
|
||||
in a document with `autofocus` is focused. Default: `false`.
|
||||
in a document with `autofocus` is focused.
|
||||
|
||||
**defaultValue** string
|
||||
**clearTextOnFocus**: bool = false
|
||||
|
||||
If `true`, clears the text field automatically when focused.
|
||||
|
||||
**defaultValue**: string
|
||||
|
||||
Provides an initial value that will change when the user starts typing. Useful
|
||||
for simple use-cases where you don't want to deal with listening to events and
|
||||
updating the `value` prop to keep the controlled state in sync.
|
||||
|
||||
**editable** bool
|
||||
**editable**: bool = true
|
||||
|
||||
If false, text is not editable. Default: `true`.
|
||||
If `false`, text is not editable (i.e., read-only).
|
||||
|
||||
**keyboardType** oneOf('default', 'email', 'numeric', 'search', 'tel', 'url')
|
||||
**keyboardType**: oneOf('default', 'email-address', 'numeric', 'phone-pad', 'search', 'url', 'web-search') = 'default'
|
||||
|
||||
Determines which keyboard to open, e.g. `email`. Default: `default`. (Not
|
||||
available when `multiline` is `true`.)
|
||||
Determines which keyboard to open. (NOTE: Safari iOS requires an ancestral
|
||||
`<form action>` element to display the `search` keyboard).
|
||||
|
||||
**multiline** bool
|
||||
(Not available when `multiline` is `true`.)
|
||||
|
||||
If true, the text input can be multiple lines. Default: `false`.
|
||||
**maxLength**: number
|
||||
|
||||
**onBlur** function
|
||||
Limits the maximum number of characters that can be entered.
|
||||
|
||||
(web) **maxNumberOfLines**: number = numberOfLines
|
||||
|
||||
Limits the maximum number of lines for a multiline `TextInput`.
|
||||
|
||||
(Requires `multiline` to be `true`.)
|
||||
|
||||
**multiline**: bool = false
|
||||
|
||||
If true, the text input can be multiple lines.
|
||||
|
||||
**numberOfLines**: number = 2
|
||||
|
||||
Sets the initial number of lines for a multiline `TextInput`.
|
||||
|
||||
(Requires `multiline` to be `true`.)
|
||||
|
||||
**onBlur**: function
|
||||
|
||||
Callback that is called when the text input is blurred.
|
||||
|
||||
**onChange** function
|
||||
**onChange**: function
|
||||
|
||||
Callback that is called when the text input's text changes.
|
||||
|
||||
**onChangeText** function
|
||||
**onChangeText**: function
|
||||
|
||||
Callback that is called when the text input's text changes. Changed text is
|
||||
passed as an argument to the callback handler.
|
||||
Callback that is called when the text input's text changes. The text is passed
|
||||
as an argument to the callback handler.
|
||||
|
||||
**onFocus** function
|
||||
**onFocus**: function
|
||||
|
||||
Callback that is called when the text input is focused.
|
||||
|
||||
**placeholder** string
|
||||
**onLayout**: function
|
||||
|
||||
The string that will be rendered before text input has been entered.
|
||||
TODO
|
||||
|
||||
**placeholderTextColor** string
|
||||
(web) **onSelectionChange**: function
|
||||
|
||||
Callback that is called when the text input's selection changes. The following
|
||||
object is passed as an argument to the callback handler.
|
||||
|
||||
```js
|
||||
{
|
||||
selectionDirection,
|
||||
selectionEnd,
|
||||
selectionStart,
|
||||
nativeEvent
|
||||
}
|
||||
```
|
||||
|
||||
**placeholder**: string
|
||||
|
||||
The string that will be rendered in an empty `TextInput` before text has been
|
||||
entered.
|
||||
|
||||
**placeholderTextColor**: string
|
||||
|
||||
The text color of the placeholder string.
|
||||
|
||||
**secureTextEntry** bool
|
||||
**secureTextEntry**: bool = false
|
||||
|
||||
If true, the text input obscures the text entered so that sensitive text like
|
||||
passwords stay secure. Default: `false`. (Not available when `multiline` is `true`.)
|
||||
passwords stay secure.
|
||||
|
||||
**style** style
|
||||
(Not available when `multiline` is `true`.)
|
||||
|
||||
[View](View.md) style
|
||||
**selectTextOnFocus**: bool = false
|
||||
|
||||
+ `color`
|
||||
+ `direction`
|
||||
+ `fontFamily`
|
||||
+ `fontSize`
|
||||
+ `fontStyle`
|
||||
+ `fontWeight`
|
||||
+ `letterSpacing`
|
||||
+ `lineHeight`
|
||||
+ `textAlign`
|
||||
+ `textDecoration`
|
||||
+ `textTransform`
|
||||
If `true`, all text will automatically be selected on focus.
|
||||
|
||||
**testID** string
|
||||
**style**: style
|
||||
|
||||
+ ...[Text#style](Text.md)
|
||||
+ `outline`
|
||||
|
||||
**testID**: string
|
||||
|
||||
Used to locate this view in end-to-end tests.
|
||||
|
||||
**value**: string
|
||||
|
||||
The value to show for the text input. `TextInput` is a controlled component,
|
||||
which means the native `value` will be forced to match this prop if provided.
|
||||
Read about how [React form
|
||||
components](https://facebook.github.io/react/docs/forms.html) work. To prevent
|
||||
user edits to the value set `editable={false}`.
|
||||
|
||||
## Instance methods
|
||||
|
||||
**blur()**
|
||||
|
||||
Blur the underlying DOM input.
|
||||
|
||||
**clear()**
|
||||
|
||||
Clear the text from the underlying DOM input.
|
||||
|
||||
**focus()**
|
||||
|
||||
Focus the underlying DOM input.
|
||||
|
||||
## Examples
|
||||
|
||||
```js
|
||||
import React, { TextInput } from 'react-native-web'
|
||||
import React, { Component, StyleSheet, TextInput } from 'react-native'
|
||||
|
||||
const { Component, PropTypes } = React
|
||||
|
||||
class AppTextInput extends Component {
|
||||
static propTypes = {
|
||||
export default class TextInputExample extends Component {
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
this.state = { isFocused: false }
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
_onBlur(e) {
|
||||
this.setState({ isFocused: false })
|
||||
}
|
||||
|
||||
_onFocus(e) {
|
||||
this.setState({ isFocused: true })
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<TextInput />
|
||||
<TextInput
|
||||
accessibilityLabel='Write a status update'
|
||||
maxNumberOfLines={5}
|
||||
multiline
|
||||
numberOfLines={2}
|
||||
onBlur={this._onBlur.bind(this)}
|
||||
onFocus={this._onFocus.bind(this)}
|
||||
placeholder={`What's happening?`}
|
||||
style={[
|
||||
styles.default
|
||||
this.state.isFocused && styles.focused
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = {
|
||||
}
|
||||
const styles = StyleSheet.create({
|
||||
default: {
|
||||
borderColor: 'gray',
|
||||
borderBottomWidth: 2
|
||||
},
|
||||
focused: {
|
||||
borderColor: 'blue'
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
# Touchable
|
||||
|
||||
## Props
|
||||
|
||||
**activeHighlight** string
|
||||
|
||||
Sets the color of the background highlight when `onPressIn` is called. The
|
||||
highlight is removed when `onPressOut` is called. Default: `transparent`.
|
||||
|
||||
**activeOpacity** number
|
||||
|
||||
Sets the opacity of the child view when `onPressIn` is called. The opacity is
|
||||
reset when `onPressOut` is called. Default: `1`.
|
||||
|
||||
**component** function or string
|
||||
|
||||
The backing component. Default: `div`.
|
||||
|
||||
**delayLongPress** number
|
||||
|
||||
Delay in ms, from `onPressIn`, before `onLongPress` is called. Default: `1000`.
|
||||
|
||||
**delayPressIn** number (TODO)
|
||||
|
||||
Delay in ms, from the start of the touch, before `onPressIn` is called. Default: `0`.
|
||||
|
||||
**delayPressOut** number (TODO)
|
||||
|
||||
Delay in ms, from the release of the touch, before `onPressOut` is called. Default: `0`.
|
||||
|
||||
**onLongPress** function
|
||||
|
||||
**onPress** function
|
||||
|
||||
**onPressIn** function
|
||||
|
||||
**onPressOut** function
|
||||
|
||||
## Examples
|
||||
|
||||
```js
|
||||
import React, { Touchable } from 'react-native-web'
|
||||
|
||||
const { Component, PropTypes } = React;
|
||||
|
||||
class Example extends Component {
|
||||
static propTypes = {
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Touchable />
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
70
docs/components/TouchableWithoutFeedback.md
Normal file
70
docs/components/TouchableWithoutFeedback.md
Normal 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.
|
||||
@@ -1,24 +1,83 @@
|
||||
# View
|
||||
|
||||
`View` is the fundamental UI building block. It is a component that supports
|
||||
style, layout with flexbox, and accessibility controls. It can be nested
|
||||
style, layout with flexbox, and accessibility controls. It can be nested
|
||||
inside another `View` and has 0-to-many children of any type.
|
||||
|
||||
Also, refer to React Native's documentation about the [Gesture Responder
|
||||
System](http://facebook.github.io/react-native/releases/0.22/docs/gesture-responder-system.html).
|
||||
|
||||
Unsupported React Native props:
|
||||
`onAccessibilityTap`,
|
||||
`hitSlop`,
|
||||
`onMagicTap`
|
||||
|
||||
## Props
|
||||
|
||||
**accessibilityLabel** string
|
||||
NOTE: `View` will transfer all other props to the rendered HTML element.
|
||||
|
||||
Overrides the text that's read by the screen reader when the user interacts
|
||||
with the element. This is implemented using `aria-label`.
|
||||
**accessibilityLabel**: string
|
||||
|
||||
**component** function, string
|
||||
Defines the text available to assistive technologies upon interaction with the
|
||||
element. (This is implemented using `aria-label`.)
|
||||
|
||||
Default is `div`.
|
||||
**accessibilityLiveRegion**: oneOf('assertive', 'off', 'polite') = 'off'
|
||||
|
||||
**pointerEvents** oneOf('auto', 'box-only', 'box-none', 'none')
|
||||
Indicates to assistive technologies whether to notify the user when the view
|
||||
changes. The values of this attribute are expressed in degrees of importance.
|
||||
When regions are specified as `polite` (recommended), updates take low
|
||||
priority. When regions are specified as `assertive`, assistive technologies
|
||||
will interrupt and immediately notify the user. (This is implemented using
|
||||
[`aria-live`](http://www.w3.org/TR/wai-aria/states_and_properties#aria-live).)
|
||||
|
||||
We deviate from the CSS spec by supporting additional `pointerEvents` modes,
|
||||
therefore `pointerEvents` is excluded from `style`.
|
||||
(web) **accessibilityRole**: oneOf(roles)
|
||||
|
||||
Allows assistive technologies to present and support interaction with the view
|
||||
in a manner that is consistent with user expectations for similar views of that
|
||||
type. For example, marking a touchable view with an `accessibilityRole` of
|
||||
`button`. (This is implemented using [ARIA roles](http://www.w3.org/TR/wai-aria/roles#role_definitions)).
|
||||
|
||||
Note: Avoid changing `accessibilityRole` values over time or after user
|
||||
actions. Generally, accessibility APIs do not provide a means of notifying
|
||||
assistive technologies of a `role` value change.
|
||||
|
||||
**accessible**: bool = true
|
||||
|
||||
When `false`, the view is hidden from assistive technologies. (This is
|
||||
implemented using `aria-hidden`.)
|
||||
|
||||
**onLayout**: function
|
||||
|
||||
(TODO)
|
||||
|
||||
**onMoveShouldSetResponder**: function
|
||||
|
||||
**onMoveShouldSetResponderCapture**: function
|
||||
|
||||
**onResponderGrant**: function
|
||||
|
||||
For most touch interactions, you'll simply want to wrap your component in
|
||||
`TouchableHighlight` or `TouchableOpacity`.
|
||||
|
||||
**onResponderMove**: function
|
||||
|
||||
**onResponderReject**: function
|
||||
|
||||
**onResponderRelease**: function
|
||||
|
||||
**onResponderTerminate**: function
|
||||
|
||||
**onResponderTerminationRequest**: function
|
||||
|
||||
**onStartShouldSetResponder**: function
|
||||
|
||||
**onStartShouldSetResponderCapture**: function
|
||||
|
||||
**pointerEvents**: oneOf('auto', 'box-only', 'box-none', 'none') = 'auto'
|
||||
|
||||
Configure the `pointerEvents` of the view. The enhanced `pointerEvents` modes
|
||||
provided are not part of the CSS spec, therefore, `pointerEvents` is excluded
|
||||
from `style`.
|
||||
|
||||
`box-none` is the equivalent of:
|
||||
|
||||
@@ -34,7 +93,7 @@ therefore `pointerEvents` is excluded from `style`.
|
||||
.box-only * { pointer-events: none }
|
||||
```
|
||||
|
||||
**style** style
|
||||
**style**: style
|
||||
|
||||
+ `alignContent`
|
||||
+ `alignItems`
|
||||
@@ -55,6 +114,8 @@ therefore `pointerEvents` is excluded from `style`.
|
||||
+ `bottom`
|
||||
+ `boxShadow`
|
||||
+ `boxSizing`
|
||||
+ `cursor`
|
||||
+ `flex` (number)
|
||||
+ `flexBasis`
|
||||
+ `flexDirection`
|
||||
+ `flexGrow`
|
||||
@@ -63,7 +124,13 @@ therefore `pointerEvents` is excluded from `style`.
|
||||
+ `height`
|
||||
+ `justifyContent`
|
||||
+ `left`
|
||||
+ `margin`
|
||||
+ `margin` (single value)
|
||||
+ `marginBottom`
|
||||
+ `marginHorizontal`
|
||||
+ `marginLeft`
|
||||
+ `marginRight`
|
||||
+ `marginTop`
|
||||
+ `marginVertical`
|
||||
+ `maxHeight`
|
||||
+ `maxWidth`
|
||||
+ `minHeight`
|
||||
@@ -73,11 +140,18 @@ therefore `pointerEvents` is excluded from `style`.
|
||||
+ `overflow`
|
||||
+ `overflowX`
|
||||
+ `overflowY`
|
||||
+ `padding`
|
||||
+ `padding` (single value)
|
||||
+ `paddingBottom`
|
||||
+ `paddingHorizontal`
|
||||
+ `paddingLeft`
|
||||
+ `paddingRight`
|
||||
+ `paddingTop`
|
||||
+ `paddingVertical`
|
||||
+ `position`
|
||||
+ `right`
|
||||
+ `top`
|
||||
+ `transform`
|
||||
+ `transformMatrix`
|
||||
+ `userSelect`
|
||||
+ `visibility`
|
||||
+ `width`
|
||||
@@ -103,18 +177,16 @@ Default:
|
||||
|
||||
(See [facebook/css-layout](https://github.com/facebook/css-layout)).
|
||||
|
||||
**testID** string
|
||||
**testID**: string
|
||||
|
||||
Used to locate this view in end-to-end tests.
|
||||
|
||||
## Examples
|
||||
|
||||
```js
|
||||
import React, { View } from 'react-native-web'
|
||||
import React, { Component, PropTypes, StyleSheet, View } from 'react-native'
|
||||
|
||||
const { Component, PropTypes } = React
|
||||
|
||||
class Example extends Component {
|
||||
export default class ViewExample extends Component {
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.row}>
|
||||
@@ -128,14 +200,12 @@ class Example extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
const styles = {
|
||||
const styles = StyleSheet.create({
|
||||
row: {
|
||||
flexDirection: 'row'
|
||||
},
|
||||
cell: {
|
||||
flexGrow: 1
|
||||
}
|
||||
}
|
||||
|
||||
export default Example
|
||||
})
|
||||
```
|
||||
|
||||
33
docs/guides/accessibility.md
Normal file
33
docs/guides/accessibility.md
Normal 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/
|
||||
115
docs/guides/direct-manipulation.md
Normal file
115
docs/guides/direct-manipulation.md
Normal 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>
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
23
docs/guides/known-issues.md
Normal file
23
docs/guides/known-issues.md
Normal 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.
|
||||
77
docs/guides/react-native.md
Normal file
77
docs/guides/react-native.md
Normal 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
97
docs/guides/rendering.md
Normal 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
220
docs/guides/style.md
Normal 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;
|
||||
}
|
||||
```
|
||||
123
docs/style.md
123
docs/style.md
@@ -1,123 +0,0 @@
|
||||
# Styling strategy
|
||||
|
||||
Using the `style` attribute would normally produce inline styles. There are
|
||||
several existing approaches to using the `style` attribute, some of which
|
||||
convert inline styles to static CSS:
|
||||
[jsxstyle](https://github.com/petehunt/jsxstyle),
|
||||
[react-free-style](https://github.com/blakeembrey/react-free-style/),
|
||||
[react-inline](https://github.com/martinandert/react-inline),
|
||||
[react-native](https://facebook.github.io/react-native/),
|
||||
[react-style](https://github.com/js-next/react-style),
|
||||
[stilr](https://github.com/kodyl/stilr).
|
||||
|
||||
## Style syntax: native vs proprietary data structure
|
||||
|
||||
React Native for Web diverges from React Native by using plain JS objects for
|
||||
styles:
|
||||
|
||||
```js
|
||||
<Text style={styles.root}>...</Text>
|
||||
|
||||
const styles = {
|
||||
root: {
|
||||
background: 'transparent',
|
||||
display: 'flex',
|
||||
flexGrow: 1,
|
||||
justifyContent: 'center'
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Most approaches to managing style in React introduce a proprietary data
|
||||
structure, often via an implementation of `Stylesheet.create`.
|
||||
|
||||
```js
|
||||
<Text style={styles.root}>...</Text>
|
||||
|
||||
const styles = Stylesheet.create({
|
||||
root: {
|
||||
background: 'transparent',
|
||||
display: 'flex',
|
||||
flexGrow: 1,
|
||||
justifyContent: 'center'
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## JS-to-CSS: conversion strategies
|
||||
|
||||
Mapping entire `style` objects to CSS rules can lead to increasingly large CSS
|
||||
files. Each new component adds new rules to the stylesheet.
|
||||
|
||||

|
||||
|
||||
One strategy for converting styles from JS to CSS is to map style objects to
|
||||
CSS rules. Another strategy is to map declarations to declarations.
|
||||
|
||||
React Native for Web currently includes a proof-of-concept implementation of
|
||||
the latter strategy. This results in smaller CSS files because all applications
|
||||
has fewer unique declarations than total declarations. Creating a new component
|
||||
with no new unique declarations results in no change to the CSS file.
|
||||
|
||||
For example:
|
||||
|
||||
```js
|
||||
<Text style={styles.root}>...</Text>
|
||||
|
||||
const styles = {
|
||||
root: {
|
||||
background: 'transparent',
|
||||
display: 'flex',
|
||||
flexGrow: 1,
|
||||
justifyContent: 'center'
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Yields:
|
||||
|
||||
```html
|
||||
<span className="_abcde _fghij _klmno _pqrst">...</span>
|
||||
```
|
||||
|
||||
And is backed by:
|
||||
|
||||
```css
|
||||
._abcde { background: transparent }
|
||||
._fghij { display: flex }
|
||||
._klmno { flex-grow: 1 }
|
||||
._pqrst { justify-content: center }
|
||||
```
|
||||
|
||||
The current implementation uses a precomputed CSS library of single-declaration
|
||||
rules, with obfuscated selectors. This handles a signficant portion of possible
|
||||
declarations. A build-time implementation would produce more accurate CSS
|
||||
files and fall through to inline styles significantly less often.
|
||||
|
||||
(CSS libraries like [Atomic CSS](http://acss.io/),
|
||||
[Basscss](http://www.basscss.com/), [SUIT CSS](https://suitcss.github.io/), and
|
||||
[tachyons](http://tachyons.io/) are attempts to limit style scope and limit
|
||||
stylesheet growth in a similar way. But they're CSS utility libraries, each with a
|
||||
particular set of classes and features to learn. All of them require developers
|
||||
to manually connect CSS classes for given styles.)
|
||||
|
||||
## Dynamic styles: use inline styles
|
||||
|
||||
Some styles cannot be resolved ahead of time and continue to rely on inline
|
||||
styles:
|
||||
|
||||
```js
|
||||
<View style={{ backgroundColor: (Math.random() > 0.5 ? 'red' : 'black') }}>...</Text>
|
||||
```
|
||||
|
||||
## Media Queries, pseudo-classes, and pseudo-elements
|
||||
|
||||
Media Queries could be replaced with `mediaMatch`. This would have the added
|
||||
benefit of co-locating breakpoint-specific DOM and style changes. Perhaps Media
|
||||
Query data could be accessed on `this.content`?
|
||||
|
||||
Pseudo-classes like `:hover` and `:focus` can be handled with JavaScript.
|
||||
|
||||
Pseudo-elements should be avoided in general, but for particular cases like
|
||||
`::placeholder` it might be necessary to reimplement it in the `TextInput`
|
||||
component (see React Native's API).
|
||||
@@ -1,19 +0,0 @@
|
||||
module.exports = {
|
||||
entry: {
|
||||
example: './example.js'
|
||||
},
|
||||
module: {
|
||||
loaders: [
|
||||
{
|
||||
test: /\.jsx?$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'babel-loader',
|
||||
query: { cacheDirectory: true }
|
||||
}
|
||||
]
|
||||
},
|
||||
output: {
|
||||
filename: 'example.js',
|
||||
path: '../dist'
|
||||
}
|
||||
}
|
||||
324
examples/2048/Game2048.js
Normal file
324
examples/2048/Game2048.js
Normal 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
201
examples/2048/GameBoard.js
Normal 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;
|
||||
321
examples/TicTacToe/TicTacToe.js
Normal file
321
examples/TicTacToe/TicTacToe.js
Normal 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;
|
||||
@@ -1,62 +1,43 @@
|
||||
import React, { Image, Swipeable, Text, TextInput, Touchable, View } from '../dist/react-native-web'
|
||||
import GridView from './GridView'
|
||||
import Heading from './Heading'
|
||||
import MediaQueryWidget from './MediaQueryWidget'
|
||||
import React, { Image, StyleSheet, ScrollView, Text, TextInput, TouchableHighlight, View } from 'react-native'
|
||||
|
||||
const { Component, PropTypes } = React
|
||||
|
||||
class Heading extends Component {
|
||||
static propTypes = {
|
||||
children: Text.propTypes.children,
|
||||
level: PropTypes.oneOf(['1', '2', '3']),
|
||||
size: PropTypes.oneOf(['xlarge', 'large', 'normal'])
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
level: '1',
|
||||
size: 'normal'
|
||||
}
|
||||
|
||||
render() {
|
||||
const { children, level, size } = this.props
|
||||
|
||||
return (
|
||||
<Text
|
||||
children={children}
|
||||
component={`h${level}`}
|
||||
style={headingStyles.size[size]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const headingStyles = {
|
||||
size: {
|
||||
xlarge: {
|
||||
fontSize: '2rem',
|
||||
marginBottom: '1em'
|
||||
},
|
||||
large: {
|
||||
fontSize: '1.5rem',
|
||||
marginBottom: '1em',
|
||||
marginTop: '1em'
|
||||
},
|
||||
normal: {
|
||||
fontSize: '1.25rem',
|
||||
marginBottom: '0.5em',
|
||||
marginTop: '0.5em'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Example extends Component {
|
||||
export default class App extends React.Component {
|
||||
static propTypes = {
|
||||
mediaQuery: React.PropTypes.object,
|
||||
style: View.propTypes.style
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.root}>
|
||||
<Heading level='1' size='xlarge'>React Native for Web: examples</Heading>
|
||||
constructor(...args) {
|
||||
super(...args)
|
||||
this.state = {
|
||||
scrollEnabled: true
|
||||
}
|
||||
}
|
||||
|
||||
<Heading level='2' size='large'>Image</Heading>
|
||||
render() {
|
||||
const { mediaQuery } = this.props
|
||||
const finalRootStyles = [
|
||||
rootStyles.common,
|
||||
mediaQuery.small.matches && rootStyles.mqSmall,
|
||||
mediaQuery.large.matches && rootStyles.mqLarge
|
||||
]
|
||||
|
||||
return (
|
||||
<ScrollView accessibilityRole='main'>
|
||||
<View style={finalRootStyles}>
|
||||
<Heading size='xlarge'>React Native for Web</Heading>
|
||||
<Text>React Native Web takes the core components from <Text
|
||||
accessibilityRole='link' href='https://facebook.github.io/react-native/'>React
|
||||
Native</Text> and brings them to the web. These components provide
|
||||
simple building blocks – touch handling, flexbox layout,
|
||||
scroll views – from which more complex components and apps can be
|
||||
constructed.</Text>
|
||||
|
||||
<MediaQueryWidget mediaQuery={mediaQuery} />
|
||||
|
||||
<Heading size='large'>Image</Heading>
|
||||
<Image
|
||||
accessibilityLabel='accessible image'
|
||||
children={<Text>Inner content</Text>}
|
||||
@@ -79,22 +60,7 @@ class Example extends Component {
|
||||
testID='Example.image'
|
||||
/>
|
||||
|
||||
<Heading level='2' size='large'>Swipeable</Heading>
|
||||
<Swipeable
|
||||
onSwiped={(e) => { console.log('Swipeable.onSwiped', e) }}
|
||||
testID={'Example.swipeable'}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: 'black',
|
||||
alignSelf: 'center',
|
||||
width: '200px',
|
||||
height: '200px'
|
||||
}}
|
||||
/>
|
||||
</Swipeable>
|
||||
|
||||
<Heading level='2' size='large'>Text</Heading>
|
||||
<Heading size='large'>Text</Heading>
|
||||
<Text
|
||||
onPress={(e) => { console.log('Text.onPress', e) }}
|
||||
testID={'Example.text'}
|
||||
@@ -119,23 +85,31 @@ class Example extends Component {
|
||||
hendrerit consequat.
|
||||
</Text>
|
||||
|
||||
<Heading level='2' size='large'>TextInput</Heading>
|
||||
<Heading size='large'>TextInput</Heading>
|
||||
<TextInput
|
||||
keyboardType='default'
|
||||
onBlur={(e) => { console.log('TextInput.onBlur', e) }}
|
||||
onChange={(e) => { console.log('TextInput.onChange', e) }}
|
||||
onChangeText={(e) => { console.log('TextInput.onChangeText', e) }}
|
||||
onFocus={(e) => { console.log('TextInput.onFocus', e) }}
|
||||
onSelectionChange={(e) => { console.log('TextInput.onSelectionChange', e) }}
|
||||
/>
|
||||
<TextInput secureTextEntry={true} />
|
||||
<TextInput secureTextEntry />
|
||||
<TextInput defaultValue='read only' editable={false} />
|
||||
<TextInput keyboardType='email-address' placeholder='you@domain.com' placeholderTextColor='red' />
|
||||
<TextInput keyboardType='numeric' />
|
||||
<TextInput keyboardType='tel' />
|
||||
<TextInput keyboardType='url' />
|
||||
<TextInput keyboardType='search' />
|
||||
<TextInput defaultValue='default value' multiline />
|
||||
<TextInput keyboardType='phone-pad' />
|
||||
<TextInput defaultValue='https://delete-me' keyboardType='url' placeholder='https://www.some-website.com' selectTextOnFocus />
|
||||
<TextInput
|
||||
defaultValue='default value'
|
||||
maxNumberOfLines={10}
|
||||
multiline
|
||||
numberOfLines={5}
|
||||
/>
|
||||
|
||||
<Heading level='2' size='large'>Touchable</Heading>
|
||||
<Touchable
|
||||
<Heading size='large'>Touchable</Heading>
|
||||
<TouchableHighlight
|
||||
accessibilityLabel={'Touchable element'}
|
||||
activeHighlight='lightblue'
|
||||
activeOpacity={0.8}
|
||||
onLongPress={(e) => { console.log('Touchable.onLongPress', e) }}
|
||||
@@ -146,10 +120,10 @@ class Example extends Component {
|
||||
<View style={styles.touchableArea}>
|
||||
<Text>Touchable area (press, long press)</Text>
|
||||
</View>
|
||||
</Touchable>
|
||||
</TouchableHighlight>
|
||||
|
||||
<Heading level='2' size='large'>View</Heading>
|
||||
<Heading level='3'>Default layout</Heading>
|
||||
<Heading size='large'>View</Heading>
|
||||
<Heading>Default layout</Heading>
|
||||
<View>
|
||||
{[ 1, 2, 3, 4, 5, 6 ].map((item, i) => {
|
||||
return (
|
||||
@@ -160,7 +134,7 @@ class Example extends Component {
|
||||
})}
|
||||
</View>
|
||||
|
||||
<Heading level='3'>Row layout</Heading>
|
||||
<Heading>Row layout</Heading>
|
||||
<View style={styles.row}>
|
||||
{[ 1, 2, 3, 4, 5, 6 ].map((item, i) => {
|
||||
return (
|
||||
@@ -171,13 +145,13 @@ class Example extends Component {
|
||||
})}
|
||||
</View>
|
||||
|
||||
<Heading level='3'>pointerEvents</Heading>
|
||||
<View style={styles.row}>
|
||||
<Heading>pointerEvents</Heading>
|
||||
<GridView alley='10px'>
|
||||
{['box-none', 'box-only', 'none'].map((value, i) => {
|
||||
return (
|
||||
<View
|
||||
accessibilityRole='link'
|
||||
children={value}
|
||||
component='a'
|
||||
href='https://google.com'
|
||||
key={i}
|
||||
pointerEvents={value}
|
||||
@@ -185,18 +159,73 @@ class Example extends Component {
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</GridView>
|
||||
|
||||
<Heading size='large'>ScrollView</Heading>
|
||||
<label>
|
||||
<input
|
||||
checked={this.state.scrollEnabled}
|
||||
onChange={() => this.setState({
|
||||
scrollEnabled: !this.state.scrollEnabled
|
||||
})}
|
||||
type='checkbox'
|
||||
/> Enable scroll
|
||||
</label>
|
||||
|
||||
<Heading>Default layout</Heading>
|
||||
<View style={styles.scrollViewContainer}>
|
||||
<ScrollView
|
||||
contentContainerStyle={styles.scrollViewContentContainerStyle}
|
||||
onScroll={e => console.log('ScrollView.onScroll', e)}
|
||||
scrollEnabled={this.state.scrollEnabled}
|
||||
scrollEventThrottle={1} // 1 event per second
|
||||
style={styles.scrollViewStyle}
|
||||
>
|
||||
{Array.from({ length: 50 }).map((item, i) => (
|
||||
<View key={i} style={styles.box}>
|
||||
<Text>{i}</Text>
|
||||
</View>
|
||||
))}
|
||||
</ScrollView>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<Heading>Horizontal layout</Heading>
|
||||
<View style={styles.scrollViewContainer}>
|
||||
<ScrollView
|
||||
contentContainerStyle={styles.scrollViewContentContainerStyle}
|
||||
horizontal
|
||||
onScroll={e => console.log('ScrollView.onScroll', e)}
|
||||
scrollEnabled={this.state.scrollEnabled}
|
||||
scrollEventThrottle={1} // 1 event per second
|
||||
style={styles.scrollViewStyle}
|
||||
>
|
||||
{Array.from({ length: 50 }).map((item, i) => (
|
||||
<View key={i} style={[ styles.box, styles.horizontalBox ]}>
|
||||
<Text>{i}</Text>
|
||||
</View>
|
||||
))}
|
||||
</ScrollView>
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const styles = {
|
||||
root: {
|
||||
fontFamily: 'sans-serif',
|
||||
maxWidth: '600px',
|
||||
margin: '0 auto'
|
||||
const rootStyles = StyleSheet.create({
|
||||
common: {
|
||||
marginVertical: 0,
|
||||
marginHorizontal: 'auto'
|
||||
},
|
||||
mqSmall: {
|
||||
maxWidth: '400px'
|
||||
},
|
||||
mqLarge: {
|
||||
maxWidth: '600px'
|
||||
}
|
||||
})
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
row: {
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap'
|
||||
@@ -205,7 +234,10 @@ const styles = {
|
||||
alignItems: 'center',
|
||||
flexGrow: 1,
|
||||
justifyContent: 'center',
|
||||
borderWidth: '1px'
|
||||
borderWidth: 1
|
||||
},
|
||||
horizontalBox: {
|
||||
width: '50px'
|
||||
},
|
||||
boxFull: {
|
||||
width: '100%'
|
||||
@@ -213,7 +245,7 @@ const styles = {
|
||||
pointerEventsBox: {
|
||||
alignItems: 'center',
|
||||
borderWidth: '1px',
|
||||
flexGrow: '1',
|
||||
flexGrow: 1,
|
||||
height: '100px',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
@@ -222,7 +254,14 @@ const styles = {
|
||||
borderWidth: 1,
|
||||
height: '200px',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
scrollViewContainer: {
|
||||
height: '200px'
|
||||
},
|
||||
scrollViewStyle: {
|
||||
borderWidth: '1px'
|
||||
},
|
||||
scrollViewContentContainerStyle: {
|
||||
padding: '10px'
|
||||
}
|
||||
}
|
||||
|
||||
React.render(<Example />, document.getElementById('react-root'))
|
||||
})
|
||||
65
examples/components/GridView.js
Normal file
65
examples/components/GridView.js
Normal file
@@ -0,0 +1,65 @@
|
||||
import React, { Component, PropTypes, StyleSheet, View } from 'react-native'
|
||||
|
||||
export default class GridView extends Component {
|
||||
static propTypes = {
|
||||
alley: PropTypes.string,
|
||||
children: PropTypes.oneOfType([
|
||||
PropTypes.element,
|
||||
PropTypes.arrayOf(PropTypes.element)
|
||||
]),
|
||||
gutter: PropTypes.string,
|
||||
style: PropTypes.object
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
alley: '0',
|
||||
gutter: '0'
|
||||
}
|
||||
|
||||
render() {
|
||||
const { alley, children, gutter, style, ...other } = this.props
|
||||
|
||||
const rootStyle = {
|
||||
...style,
|
||||
...styles.root
|
||||
}
|
||||
|
||||
const contentContainerStyle = {
|
||||
...styles.contentContainer,
|
||||
marginHorizontal: `calc(-0.5 * ${alley})`,
|
||||
paddingHorizontal: `${gutter}`
|
||||
}
|
||||
|
||||
const newChildren = React.Children.map(children, (child) => {
|
||||
return child && React.cloneElement(child, {
|
||||
style: {
|
||||
...child.props.style,
|
||||
...styles.column,
|
||||
marginHorizontal: `calc(0.5 * ${alley})`
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return (
|
||||
<View className='GridView' {...other} style={rootStyle}>
|
||||
<View style={contentContainerStyle}>
|
||||
{newChildren}
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
overflow: 'hidden'
|
||||
},
|
||||
contentContainer: {
|
||||
flexDirection: 'row',
|
||||
flexGrow: 1
|
||||
},
|
||||
// distribute all space (rather than extra space)
|
||||
column: {
|
||||
flexBasis: '0%'
|
||||
}
|
||||
})
|
||||
34
examples/components/Heading.js
Normal file
34
examples/components/Heading.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import React, { StyleSheet, Text } from 'react-native'
|
||||
|
||||
const Heading = ({ children, size = 'normal' }) => (
|
||||
<Text
|
||||
accessibilityRole='heading'
|
||||
children={children}
|
||||
style={{ ...styles.root, ...sizeStyles[size] }}
|
||||
/>
|
||||
)
|
||||
|
||||
const sizeStyles = StyleSheet.create({
|
||||
xlarge: {
|
||||
fontSize: '2rem',
|
||||
marginBottom: '1em'
|
||||
},
|
||||
large: {
|
||||
fontSize: '1.5rem',
|
||||
marginBottom: '1em',
|
||||
marginTop: '1em'
|
||||
},
|
||||
normal: {
|
||||
fontSize: '1.25rem',
|
||||
marginBottom: '0.5em',
|
||||
marginTop: '0.5em'
|
||||
}
|
||||
})
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
fontFamily: '"Helvetica Neue", arial, sans-serif'
|
||||
}
|
||||
})
|
||||
|
||||
export default Heading
|
||||
39
examples/components/MediaQueryWidget.js
Normal file
39
examples/components/MediaQueryWidget.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import React, { StyleSheet, Text, View } from 'react-native'
|
||||
|
||||
const MediaQueryWidget = ({ mediaQuery = {} }) => {
|
||||
const active = Object.keys(mediaQuery).reduce((active, alias) => {
|
||||
if (mediaQuery[alias].matches) {
|
||||
active = {
|
||||
alias,
|
||||
mql: mediaQuery[alias]
|
||||
}
|
||||
}
|
||||
return active
|
||||
}, {})
|
||||
|
||||
return (
|
||||
<View style={styles.root}>
|
||||
<Text style={styles.heading}>Active Media Query</Text>
|
||||
<Text style={styles.text}>{`"${active.alias}"`} {active.mql && active.mql.media}</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
alignItems: 'center',
|
||||
borderWidth: 1,
|
||||
marginVertical: 10,
|
||||
padding: 10
|
||||
},
|
||||
heading: {
|
||||
fontWeight: 'bold',
|
||||
padding: 5,
|
||||
textAlign: 'center'
|
||||
},
|
||||
text: {
|
||||
textAlign: 'center'
|
||||
}
|
||||
})
|
||||
|
||||
export default MediaQueryWidget
|
||||
@@ -1,5 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>React Native for Web</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<div id="react-root"></div>
|
||||
<script src="../dist/example.js"></script>
|
||||
<script src="/bundle.js"></script>
|
||||
7
examples/index.js
Normal file
7
examples/index.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import React, { AppRegistry } from 'react-native'
|
||||
import Game2048 from './2048/Game2048'
|
||||
import TicTacToeApp from './TicTacToe/TicTacToe'
|
||||
|
||||
AppRegistry.runApplication('Game2048', {
|
||||
rootTag: document.getElementById('react-root')
|
||||
})
|
||||
38
examples/webpack.config.js
Normal file
38
examples/webpack.config.js
Normal 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')
|
||||
}
|
||||
}
|
||||
}
|
||||
55
karma.config.js
Normal file
55
karma.config.js
Normal file
@@ -0,0 +1,55 @@
|
||||
var webpack = require('webpack')
|
||||
|
||||
var testEntry = 'tests.webpack.js'
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
browsers: process.env.TRAVIS ? [ 'Firefox' ] : [ 'Chrome' ],
|
||||
browserNoActivityTimeout: 60000,
|
||||
client: {
|
||||
captureConsole: true,
|
||||
mocha: { ui: 'tdd' },
|
||||
useIframe: true
|
||||
},
|
||||
files: [
|
||||
testEntry
|
||||
],
|
||||
frameworks: [ 'mocha' ],
|
||||
plugins: [
|
||||
'karma-chrome-launcher',
|
||||
'karma-firefox-launcher',
|
||||
'karma-mocha',
|
||||
'karma-sourcemap-loader',
|
||||
'karma-spec-reporter',
|
||||
'karma-webpack'
|
||||
],
|
||||
preprocessors: {
|
||||
[testEntry]: [ 'webpack', 'sourcemap' ]
|
||||
},
|
||||
reporters: process.env.TRAVIS ? [ 'dots' ] : [ 'spec' ],
|
||||
singleRun: true,
|
||||
webpack: {
|
||||
devtool: 'inline-source-map',
|
||||
module: {
|
||||
loaders: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'babel',
|
||||
query: { cacheDirectory: true }
|
||||
}
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
NODE_ENV: JSON.stringify('test')
|
||||
}
|
||||
})
|
||||
]
|
||||
},
|
||||
webpackServer: {
|
||||
noInfo: true
|
||||
}
|
||||
})
|
||||
}
|
||||
97
package.json
97
package.json
@@ -1,57 +1,76 @@
|
||||
{
|
||||
"name": "react-native-web",
|
||||
"version": "0.0.5",
|
||||
"version": "0.0.25",
|
||||
"description": "React Native for Web",
|
||||
"main": "dist/react-native-web.js",
|
||||
"main": "dist/index.js",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"prepublish": "NODE_ENV=production npm run build",
|
||||
"build": "rm -rf ./dist && webpack --config config/webpack.config.js --sort-assets-by --progress",
|
||||
"example": "cd example && webpack --config webpack.config.js",
|
||||
"lint": "eslint .",
|
||||
"specs": "NODE_ENV=test karma start config/karma.config.js",
|
||||
"specs:watch": "npm run specs -- --no-single-run",
|
||||
"start": "webpack-dev-server --config config/webpack.config.js --inline --hot --colors --quiet",
|
||||
"test": "npm run specs && npm run lint"
|
||||
"build": "rm -rf ./dist && mkdir dist && babel src -d dist --ignore **/__tests__,src/modules/specHelpers",
|
||||
"build:umd": "webpack --config 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": {
|
||||
"react": "^0.13.3",
|
||||
"react-swipeable": "^3.0.2",
|
||||
"react-tappable": "^0.6.0"
|
||||
"fbjs": "0.6.x || 0.7.x",
|
||||
"inline-style-prefix-all": "1.0.5",
|
||||
"lodash.debounce": "^4.0.3",
|
||||
"react-textarea-autosize": "^3.1.0",
|
||||
"react-timer-mixin": "^0.13.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer-core": "^5.2.1",
|
||||
"babel-core": "^5.8.23",
|
||||
"babel-eslint": "^4.1.1",
|
||||
"babel-loader": "^5.3.2",
|
||||
"babel-runtime": "^5.8.20",
|
||||
"css-loader": "^0.17.0",
|
||||
"eslint": "^1.3.1",
|
||||
"eslint-config-standard": "^4.3.1",
|
||||
"eslint-config-standard-react": "^1.0.4",
|
||||
"eslint-plugin-react": "^3.3.1",
|
||||
"eslint-plugin-standard": "^1.3.0",
|
||||
"extract-text-webpack-plugin": "^0.8.2",
|
||||
"karma": "^0.13.9",
|
||||
"karma-chrome-launcher": "^0.2.0",
|
||||
"karma-firefox-launcher": "^0.1.6",
|
||||
"karma-mocha": "^0.2.0",
|
||||
"karma-sourcemap-loader": "^0.3.5",
|
||||
"babel-cli": "^6.3.17",
|
||||
"babel-core": "^6.3.13",
|
||||
"babel-eslint": "^4.1.6",
|
||||
"babel-loader": "^6.2.0",
|
||||
"babel-plugin-transform-decorators-legacy": "^1.3.4",
|
||||
"babel-preset-es2015": "^6.3.13",
|
||||
"babel-preset-react": "^6.3.13",
|
||||
"babel-preset-stage-1": "^6.3.13",
|
||||
"babel-runtime": "^6.3.19",
|
||||
"eslint": "^1.10.3",
|
||||
"eslint-config-standard": "^4.4.0",
|
||||
"eslint-config-standard-react": "^1.2.1",
|
||||
"eslint-plugin-react": "^3.13.1",
|
||||
"eslint-plugin-standard": "^1.3.1",
|
||||
"karma": "^0.13.16",
|
||||
"karma-browserstack-launcher": "^0.1.8",
|
||||
"karma-chrome-launcher": "^0.2.2",
|
||||
"karma-firefox-launcher": "^0.1.7",
|
||||
"karma-mocha": "^0.2.1",
|
||||
"karma-sourcemap-loader": "^0.3.6",
|
||||
"karma-spec-reporter": "0.0.23",
|
||||
"karma-webpack": "^1.7.0",
|
||||
"mocha": "^2.3.0",
|
||||
"node-libs-browser": "^0.5.2",
|
||||
"object-assign": "^4.0.1",
|
||||
"postcss-loader": "^0.4.4",
|
||||
"style-loader": "^0.12.3",
|
||||
"webpack": "^1.12.1",
|
||||
"webpack-dev-server": "^1.10.1"
|
||||
"mocha": "^2.3.4",
|
||||
"node-libs-browser": "^0.5.3",
|
||||
"react": "^0.14.3",
|
||||
"react-addons-test-utils": "^0.14.3",
|
||||
"react-dom": "^0.14.3",
|
||||
"react-media-queries": "^2.0.1",
|
||||
"webpack": "^1.12.9",
|
||||
"webpack-dev-server": "^1.14.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^0.14.3",
|
||||
"react-dom": "^0.14.3"
|
||||
},
|
||||
"author": "Nicolas Gallagher",
|
||||
"license": "MIT",
|
||||
"license": "BSD-3-Clause",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/necolas/react-native-web.git"
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"react"
|
||||
],
|
||||
"keywords": [
|
||||
"react",
|
||||
"react-component",
|
||||
"react-native",
|
||||
"web"
|
||||
]
|
||||
}
|
||||
|
||||
23
src/__tests__/index-test.js
Normal file
23
src/__tests__/index-test.js
Normal 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)
|
||||
})
|
||||
})
|
||||
})
|
||||
1654
src/apis/Animated/AnimatedImplementation.js
Normal file
1654
src/apis/Animated/AnimatedImplementation.js
Normal file
File diff suppressed because it is too large
Load Diff
288
src/apis/Animated/Interpolation.js
Normal file
288
src/apis/Animated/Interpolation.js
Normal 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;
|
||||
103
src/apis/Animated/SpringConfig.js
Normal file
103
src/apis/Animated/SpringConfig.js
Normal 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,
|
||||
};
|
||||
19
src/apis/Animated/index.js
Normal file
19
src/apis/Animated/index.js
Normal 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)
|
||||
}
|
||||
15
src/apis/Animated/polyfills/Set.js
Normal file
15
src/apis/Animated/polyfills/Set.js
Normal 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
|
||||
49
src/apis/AppRegistry/ReactNativeApp.js
Normal file
49
src/apis/AppRegistry/ReactNativeApp.js
Normal 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
|
||||
0
src/apis/AppRegistry/__tests__/index-test.js
Normal file
0
src/apis/AppRegistry/__tests__/index-test.js
Normal file
20
src/apis/AppRegistry/__tests__/renderApplication-test.js
Normal file
20
src/apis/AppRegistry/__tests__/renderApplication-test.js
Normal 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)
|
||||
})
|
||||
})
|
||||
92
src/apis/AppRegistry/index.js
Normal file
92
src/apis/AppRegistry/index.js
Normal 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
|
||||
48
src/apis/AppRegistry/renderApplication.js
Normal file
48
src/apis/AppRegistry/renderApplication.js
Normal 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 }
|
||||
}
|
||||
5
src/apis/AppState/__tests__/index-test.js
Normal file
5
src/apis/AppState/__tests__/index-test.js
Normal file
@@ -0,0 +1,5 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
suite('apis/AppState', () => {
|
||||
test.skip('NO TEST COVERAGE', () => {})
|
||||
})
|
||||
31
src/apis/AppState/index.js
Normal file
31
src/apis/AppState/index.js
Normal 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
|
||||
5
src/apis/AsyncStorage/__tests__/index-test.js
Normal file
5
src/apis/AsyncStorage/__tests__/index-test.js
Normal file
@@ -0,0 +1,5 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
suite('apis/AsyncStorage', () => {
|
||||
test.skip('NO TEST COVERAGE', () => {})
|
||||
})
|
||||
161
src/apis/AsyncStorage/index.js
Normal file
161
src/apis/AsyncStorage/index.js
Normal 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
|
||||
5
src/apis/Dimensions/__tests__/index-test.js
Normal file
5
src/apis/Dimensions/__tests__/index-test.js
Normal file
@@ -0,0 +1,5 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
suite('apis/Dimensions', () => {
|
||||
test.skip('NO TEST COVERAGE', () => {})
|
||||
})
|
||||
43
src/apis/Dimensions/index.js
Normal file
43
src/apis/Dimensions/index.js
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Nicolas Gallagher.
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import debounce from 'lodash.debounce'
|
||||
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment'
|
||||
import invariant from 'fbjs/lib/invariant'
|
||||
|
||||
const win = ExecutionEnvironment.canUseDOM ? window : { screen: {} }
|
||||
|
||||
const dimensions = {}
|
||||
|
||||
class Dimensions {
|
||||
static get(dimension: string): Object {
|
||||
invariant(dimensions[dimension], 'No dimension set for key ' + dimension)
|
||||
return dimensions[dimension]
|
||||
}
|
||||
|
||||
static set(): void {
|
||||
dimensions.window = {
|
||||
fontScale: 1,
|
||||
height: win.innerHeight,
|
||||
scale: win.devicePixelRatio || 1,
|
||||
width: win.innerWidth
|
||||
}
|
||||
|
||||
dimensions.screen = {
|
||||
fontScale: 1,
|
||||
height: win.screen.height,
|
||||
scale: win.devicePixelRatio || 1,
|
||||
width: win.screen.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Dimensions.set()
|
||||
ExecutionEnvironment.canUseDOM && window.addEventListener('resize', debounce(Dimensions.set, 50))
|
||||
|
||||
module.exports = Dimensions
|
||||
83
src/apis/Easing/bezier.js
Normal file
83
src/apis/Easing/bezier.js
Normal 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
155
src/apis/Easing/index.js
Normal 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;
|
||||
48
src/apis/InteractionManager/index.js
Normal file
48
src/apis/InteractionManager/index.js
Normal 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
|
||||
5
src/apis/NetInfo/__tests__/index-test.js
Normal file
5
src/apis/NetInfo/__tests__/index-test.js
Normal file
@@ -0,0 +1,5 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
suite('apis/NetInfo', () => {
|
||||
test.skip('NO TEST COVERAGE', () => {})
|
||||
})
|
||||
85
src/apis/NetInfo/index.js
Normal file
85
src/apis/NetInfo/index.js
Normal 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(null, true), false)
|
||||
window.addEventListener('offline', handler.bind(null, false), false)
|
||||
|
||||
return {
|
||||
remove: () => NetInfo.isConnected.removeEventListener(type, handler)
|
||||
}
|
||||
},
|
||||
|
||||
removeEventListener(type: string, handler: Function): void {
|
||||
invariant(eventTypes.indexOf(type) !== -1, 'Trying to subscribe to unknown event: "%s"', type)
|
||||
window.removeEventListener('online', handler.bind(null, true), false)
|
||||
window.removeEventListener('offline', handler.bind(null, false), false)
|
||||
},
|
||||
|
||||
fetch(): Promise {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
resolve(window.navigator.onLine)
|
||||
} catch (err) {
|
||||
resolve(true)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = NetInfo
|
||||
124
src/apis/PanResponder/TouchHistoryMath.js
Normal file
124
src/apis/PanResponder/TouchHistoryMath.js
Normal 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;
|
||||
392
src/apis/PanResponder/index.js
Normal file
392
src/apis/PanResponder/index.js
Normal 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;
|
||||
59
src/apis/PanResponder/injectResponderEventPlugin.js
Normal file
59
src/apis/PanResponder/injectResponderEventPlugin.js
Normal 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
|
||||
})
|
||||
91
src/apis/PanResponder/normalizeNativeEvent.js
Normal file
91
src/apis/PanResponder/normalizeNativeEvent.js
Normal 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
|
||||
5
src/apis/PixelRatio/__tests__/index-test.js
Normal file
5
src/apis/PixelRatio/__tests__/index-test.js
Normal file
@@ -0,0 +1,5 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
suite('apis/PixelRatio', () => {
|
||||
test.skip('NO TEST COVERAGE', () => {})
|
||||
})
|
||||
49
src/apis/PixelRatio/index.js
Normal file
49
src/apis/PixelRatio/index.js
Normal 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
|
||||
8
src/apis/Platform/index.js
Normal file
8
src/apis/Platform/index.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment'
|
||||
|
||||
const Platform = {
|
||||
OS: 'web',
|
||||
userAgent: canUseDOM ? window.navigator.userAgent : ''
|
||||
}
|
||||
|
||||
module.exports = Platform
|
||||
25
src/apis/StyleSheet/BorderPropTypes.js
Normal file
25
src/apis/StyleSheet/BorderPropTypes.js
Normal 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
|
||||
62
src/apis/StyleSheet/ColorPropType.js
Normal file
62
src/apis/StyleSheet/ColorPropType.js
Normal 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
|
||||
26
src/apis/StyleSheet/EdgeInsetsPropType.js
Normal file
26
src/apis/StyleSheet/EdgeInsetsPropType.js
Normal 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;
|
||||
54
src/apis/StyleSheet/LayoutPropTypes.js
Normal file
54
src/apis/StyleSheet/LayoutPropTypes.js
Normal 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
|
||||
24
src/apis/StyleSheet/PointPropType.js
Normal file
24
src/apis/StyleSheet/PointPropType.js
Normal 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;
|
||||
22
src/apis/StyleSheet/StyleSheetPropType.js
Normal file
22
src/apis/StyleSheet/StyleSheetPropType.js
Normal 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)
|
||||
}
|
||||
}
|
||||
101
src/apis/StyleSheet/StyleSheetRegistry.js
Normal file
101
src/apis/StyleSheet/StyleSheetRegistry.js
Normal 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
|
||||
69
src/apis/StyleSheet/StyleSheetValidation.js
Normal file
69
src/apis/StyleSheet/StyleSheetValidation.js
Normal 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
|
||||
49
src/apis/StyleSheet/TransformPropTypes.js
Normal file
49
src/apis/StyleSheet/TransformPropTypes.js
Normal 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
|
||||
55
src/apis/StyleSheet/__tests__/StyleSheetRegistry-test.js
Normal file
55
src/apis/StyleSheet/__tests__/StyleSheetRegistry-test.js
Normal 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)
|
||||
})
|
||||
})
|
||||
45
src/apis/StyleSheet/__tests__/expandStyle-test.js
Normal file
45
src/apis/StyleSheet/__tests__/expandStyle-test.js
Normal 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)
|
||||
})
|
||||
})
|
||||
51
src/apis/StyleSheet/__tests__/flattenStyle-test.js
Normal file
51
src/apis/StyleSheet/__tests__/flattenStyle-test.js
Normal 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)
|
||||
})
|
||||
})
|
||||
16
src/apis/StyleSheet/__tests__/hyphenate-test.js
Normal file
16
src/apis/StyleSheet/__tests__/hyphenate-test.js
Normal file
@@ -0,0 +1,16 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
import assert from 'assert'
|
||||
import hyphenate from '../hyphenate'
|
||||
|
||||
suite('apis/StyleSheet/hyphenate', () => {
|
||||
test('style property', () => {
|
||||
assert.equal(hyphenate('alignItems'), 'align-items')
|
||||
assert.equal(hyphenate('color'), 'color')
|
||||
})
|
||||
test('vendor prefixed style property', () => {
|
||||
assert.equal(hyphenate('MozTransition'), '-moz-transition')
|
||||
assert.equal(hyphenate('msTransition'), '-ms-transition')
|
||||
assert.equal(hyphenate('WebkitTransition'), '-webkit-transition')
|
||||
})
|
||||
})
|
||||
51
src/apis/StyleSheet/__tests__/index-test.js
Normal file
51
src/apis/StyleSheet/__tests__/index-test.js
Normal 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;}`
|
||||
)
|
||||
})
|
||||
})
|
||||
13
src/apis/StyleSheet/__tests__/normalizeValue-test.js
Normal file
13
src/apis/StyleSheet/__tests__/normalizeValue-test.js
Normal file
@@ -0,0 +1,13 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
import assert from 'assert'
|
||||
import normalizeValue from '../normalizeValue'
|
||||
|
||||
suite('apis/StyleSheet/normalizeValue', () => {
|
||||
test('normalizes property values requiring units', () => {
|
||||
assert.deepEqual(normalizeValue('margin', 0), '0px')
|
||||
})
|
||||
test('ignores unitless property values', () => {
|
||||
assert.deepEqual(normalizeValue('flexGrow', 1), 1)
|
||||
})
|
||||
})
|
||||
31
src/apis/StyleSheet/__tests__/processTransform-test.js
Normal file
31
src/apis/StyleSheet/__tests__/processTransform-test.js
Normal 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)' }
|
||||
)
|
||||
})
|
||||
})
|
||||
70
src/apis/StyleSheet/createStrictShapeTypeChecker.js
Normal file
70
src/apis/StyleSheet/createStrictShapeTypeChecker.js
Normal 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
|
||||
}
|
||||
65
src/apis/StyleSheet/expandStyle.js
Normal file
65
src/apis/StyleSheet/expandStyle.js
Normal 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
|
||||
31
src/apis/StyleSheet/flattenStyle.js
Normal file
31
src/apis/StyleSheet/flattenStyle.js
Normal 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
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user