mirror of
https://github.com/zhigang1992/react-native-web.git
synced 2026-03-31 22:42:37 +08:00
Compare commits
109 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e9536b611 | ||
|
|
d15dafc108 | ||
|
|
c9c1aab97e | ||
|
|
a2903f9d30 | ||
|
|
c7771ac64f | ||
|
|
c8129c2a99 | ||
|
|
b793737393 | ||
|
|
2a4d1c81d8 | ||
|
|
a8a25d66ea | ||
|
|
e06d7a9650 | ||
|
|
c2501f2bc2 | ||
|
|
c51e7f1965 | ||
|
|
dfff6b3780 | ||
|
|
5f6b4a746a | ||
|
|
f077907dd4 | ||
|
|
a94367bdcb | ||
|
|
65febbbc52 | ||
|
|
b14d2e5bd8 | ||
|
|
7c83ba162d | ||
|
|
3ffc005a7b | ||
|
|
50a70ad02f | ||
|
|
768e895701 | ||
|
|
af5fde994d | ||
|
|
c3d0763944 | ||
|
|
0aba506725 | ||
|
|
91032d8565 | ||
|
|
0696721488 | ||
|
|
fe18830ce6 | ||
|
|
1b86d02300 | ||
|
|
c56b472258 | ||
|
|
b00132f007 | ||
|
|
8b8f8f0374 | ||
|
|
8e94af34e1 | ||
|
|
7ffaf592d5 | ||
|
|
a1017fa785 | ||
|
|
5db300df35 | ||
|
|
214d862e61 | ||
|
|
4ef5453b33 | ||
|
|
a27671d7cf | ||
|
|
8d2a650670 | ||
|
|
4cc1c983e8 | ||
|
|
37413fd55f | ||
|
|
07ff0ea104 | ||
|
|
1a87657500 | ||
|
|
5e4c8e520a | ||
|
|
236121e32c | ||
|
|
39c76ca50c | ||
|
|
e65f91d849 | ||
|
|
a535c558d8 | ||
|
|
a74be91b7c | ||
|
|
8eaaf28a32 | ||
|
|
16d448dc5b | ||
|
|
ea75cced13 | ||
|
|
cfc56a1354 | ||
|
|
b1cd92a65d | ||
|
|
d87f71ebc1 | ||
|
|
a2cafe56fc | ||
|
|
351c0ac3d4 | ||
|
|
877c0d2818 | ||
|
|
3afc5d5de6 | ||
|
|
edf3b9b7ff | ||
|
|
518a85bf1b | ||
|
|
ba75acb66a | ||
|
|
bc68b0b6f4 | ||
|
|
44ecbc072e | ||
|
|
4cf4905fc2 | ||
|
|
509920be4b | ||
|
|
04e3c23e67 | ||
|
|
32f454de66 | ||
|
|
1273bfc7cf | ||
|
|
dc7f526f6b | ||
|
|
7cda89c5ce | ||
|
|
695eba45af | ||
|
|
92a2cb274a | ||
|
|
b1ca04d11e | ||
|
|
22ab70ea6f | ||
|
|
49f36d8eb1 | ||
|
|
80ba119b83 | ||
|
|
c30b67f6db | ||
|
|
4580f93199 | ||
|
|
4c46126ffe | ||
|
|
f8d5c15405 | ||
|
|
dc54e03625 | ||
|
|
4d5819ae28 | ||
|
|
5c482ef3be | ||
|
|
f51592f96e | ||
|
|
6bffe1775f | ||
|
|
7e75d037f2 | ||
|
|
7536508fe3 | ||
|
|
945fff0015 | ||
|
|
5032ed6fe1 | ||
|
|
8c7cdbf4be | ||
|
|
e5d8857bcc | ||
|
|
cda8d05bb7 | ||
|
|
049edc4611 | ||
|
|
e76d5a4e6c | ||
|
|
f71dae7d93 | ||
|
|
94d31beaf4 | ||
|
|
f5f9389728 | ||
|
|
fdbd19a4af | ||
|
|
36eafbc2f5 | ||
|
|
bca3398c1c | ||
|
|
722d77e8e5 | ||
|
|
d65c92eea9 | ||
|
|
8dd39c681c | ||
|
|
0b1759363d | ||
|
|
abd1227a94 | ||
|
|
8352c7cbda | ||
|
|
89f5a13891 |
3
.babelrc
3
.babelrc
@@ -1,5 +1,8 @@
|
||||
{
|
||||
"presets": [
|
||||
"react-native"
|
||||
],
|
||||
"plugins": [
|
||||
[ "transform-react-remove-prop-types", { "mode": "wrap" } ]
|
||||
]
|
||||
}
|
||||
|
||||
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -13,5 +13,6 @@ https://github.com/necolas/react-native-web/CONTRIBUTING.md
|
||||
|
||||
- [ ] includes documentation
|
||||
- [ ] includes tests
|
||||
- [ ] includes benchmark reports
|
||||
- [ ] includes an interactive example
|
||||
- [ ] includes screenshots/videos
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
/dist
|
||||
/dist-examples
|
||||
/dist-performance
|
||||
/node_modules
|
||||
|
||||
@@ -5,5 +5,4 @@ before_script:
|
||||
- export DISPLAY=:99.0
|
||||
- sh -e /etc/init.d/xvfb start
|
||||
script:
|
||||
- npm run lint
|
||||
- npm test
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# Contributing
|
||||
|
||||
We are open to, and grateful for, any contributions made by the community.
|
||||
|
||||
## Reporting Issues and Asking Questions
|
||||
|
||||
Before opening an issue, please search the [issue
|
||||
@@ -19,12 +17,24 @@ Fork, then clone the repo:
|
||||
git clone https://github.com/your-username/react-native-web.git
|
||||
```
|
||||
|
||||
Install dependencies (requires [yarn](https://yarnpkg.com/en/docs/install):
|
||||
|
||||
```
|
||||
yarn
|
||||
```
|
||||
|
||||
Run the examples:
|
||||
|
||||
```
|
||||
npm run examples
|
||||
```
|
||||
|
||||
Run the benchmarks in a browser by opening `./performance/index.html` after running:
|
||||
|
||||
```
|
||||
npm run build:performance
|
||||
```
|
||||
|
||||
### Building
|
||||
|
||||
```
|
||||
@@ -51,7 +61,7 @@ To continuously watch and run tests, run the following:
|
||||
npm run test:watch
|
||||
```
|
||||
|
||||
To perform linting, run the following:
|
||||
To perform only linting, run the following:
|
||||
|
||||
```
|
||||
npm run lint
|
||||
|
||||
@@ -27,7 +27,7 @@ online with [React Native for Web: Playground](http://codepen.io/necolas/pen/PZz
|
||||
To install in your app:
|
||||
|
||||
```
|
||||
npm install --save react react-native-web
|
||||
npm install --save react@15.4 react-native-web
|
||||
```
|
||||
|
||||
Read the [Client and Server rendering](docs/guides/rendering.md) guide.
|
||||
@@ -53,6 +53,7 @@ Exported modules:
|
||||
|
||||
* Components
|
||||
* [`ActivityIndicator`](docs/components/ActivityIndicator.md)
|
||||
* [`Button`](docs/components/Button.md)
|
||||
* [`Image`](docs/components/Image.md)
|
||||
* [`ListView`](docs/components/ListView.md)
|
||||
* [`ProgressBar`](docs/components/ProgressBar.md)
|
||||
@@ -69,6 +70,7 @@ Exported modules:
|
||||
* [`AppRegistry`](docs/apis/AppRegistry.md)
|
||||
* [`AppState`](docs/apis/AppState.md)
|
||||
* [`AsyncStorage`](docs/apis/AsyncStorage.md)
|
||||
* [`Clipboard`](docs/apis/Clipboard.md)
|
||||
* [`Dimensions`](docs/apis/Dimensions.md)
|
||||
* [`I18nManager`](docs/apis/I18nManager.md)
|
||||
* [`NativeMethods`](docs/apis/NativeMethods.md)
|
||||
@@ -80,6 +82,7 @@ Exported modules:
|
||||
* [`Vibration`](docs/apis/Vibration.md)
|
||||
|
||||
<span id="#why"></span>
|
||||
|
||||
## Why?
|
||||
|
||||
There are many different teams at Twitter building web applications with React.
|
||||
@@ -142,6 +145,7 @@ AppRegistry.runApplication('MyApp', { rootTag: document.getElementById('react-ro
|
||||
* [react-native-web-player](https://github.com/dabbott/react-native-web-player)
|
||||
* [react-web](https://github.com/taobaofed/react-web)
|
||||
* [react-native-for-web](https://github.com/KodersLab/react-native-for-web)
|
||||
* [rhinos-app](https://github.com/rhinos-app/rhinos-app-dev)
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
`AppRegistry` is the control point for registering, running, prerendering, and
|
||||
unmounting all apps. App root components should register themselves with
|
||||
`AppRegistry.registerComponent`. Apps can be run by invoking
|
||||
`AppRegistry.runApplication`, and prerendered by invoking
|
||||
`AppRegistry.prerenderApplication` (see the [client and server rendering
|
||||
`AppRegistry.runApplication` (see the [client and server rendering
|
||||
guide](../guides/rendering.md) for more details).
|
||||
|
||||
To "stop" an application when a view should be destroyed, call
|
||||
@@ -13,14 +12,11 @@ into `runApplication`. These should always be used as a pair.
|
||||
|
||||
## Methods
|
||||
|
||||
(web) static **prerenderApplication**(appKey:string, appParameters: object)
|
||||
(web) static **getApplication**(appKey:string, appParameters: object)
|
||||
|
||||
Renders the given application to an HTML string. Use this for server-side
|
||||
rendering. Return object is of type `{ html: string; style: string;
|
||||
styleElement: ReactComponent }`. `html` is the prerendered HTML, `style` is the
|
||||
prerendered style sheet, and `styleElement` is a React Component. It's
|
||||
recommended that you use `styleElement` to render the style sheet in an app
|
||||
shell.
|
||||
Returns the given application element. Use this for server-side rendering.
|
||||
Return object is of type `{ element: ReactElement; stylesheet: ReactElement }`.
|
||||
It's recommended that you use `sheetsheet` to render the style sheet in an app
|
||||
|
||||
static **registerConfig**(config: Array<AppConfig>)
|
||||
|
||||
|
||||
16
docs/apis/Clipboard.md
Normal file
16
docs/apis/Clipboard.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# Clipboard
|
||||
|
||||
Clipboard gives you an interface for setting to the clipboard. (Getting
|
||||
clipboard content is not supported on web.)
|
||||
|
||||
## Methods
|
||||
|
||||
static **getString**()
|
||||
|
||||
Returns a `Promise` of an empty string.
|
||||
|
||||
static **setString**(content: string): boolean
|
||||
|
||||
Copies a string to the clipboard. On web, some browsers may not support copying
|
||||
to the clipboard, therefore, this function returns a boolean to indicate if the
|
||||
copy was successful.
|
||||
@@ -15,9 +15,9 @@ Each key of the object passed to `create` must define a style object.
|
||||
|
||||
Flattens an array of styles into a single style object.
|
||||
|
||||
**render**: function
|
||||
**renderToString**: function
|
||||
|
||||
Returns a React `<style>` element for use in server-side rendering.
|
||||
Returns a string of the stylesheet for use in server-side rendering.
|
||||
|
||||
## Properties
|
||||
|
||||
|
||||
39
docs/components/Button.md
Normal file
39
docs/components/Button.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Button
|
||||
|
||||
A basic button component. Supports a minimal level of customization. You can
|
||||
build your own custom button using `TouchableOpacity` or
|
||||
`TouchableNativeFeedback`.
|
||||
|
||||
## Props
|
||||
|
||||
**accessibilityLabel**: string
|
||||
|
||||
Defines the text available to assistive technologies upon interaction with the
|
||||
element. (This is implemented using `aria-label`.)
|
||||
|
||||
**color**: string
|
||||
|
||||
Background color of the button.
|
||||
|
||||
**disabled**: bool = false
|
||||
|
||||
If true, disable all interactions for this component
|
||||
|
||||
**onPress**: function
|
||||
|
||||
This function is called on press.
|
||||
|
||||
**title**: string
|
||||
|
||||
Text to display inside the button.
|
||||
|
||||
## Examples
|
||||
|
||||
```js
|
||||
<Button
|
||||
accessibilityLabel="Learn more about this purple button"
|
||||
color="#841584"
|
||||
onPress={onPressLearnMore}
|
||||
title="Learn More"
|
||||
/>
|
||||
```
|
||||
@@ -75,6 +75,23 @@ Example usage:
|
||||
<Image resizeMode={Image.resizeMode.contain} />
|
||||
```
|
||||
|
||||
## Methods
|
||||
|
||||
static **getSize**(uri: string, success: (width, height) => {}, failure: function)
|
||||
|
||||
Retrieve the width and height (in pixels) of an image prior to displaying it.
|
||||
This method can fail if the image cannot be found, or fails to download.
|
||||
|
||||
(In order to retrieve the image dimensions, the image may first need to be
|
||||
loaded or downloaded, after which it will be cached. This means that in
|
||||
principle you could use this method to preload images, however it is not
|
||||
optimized for that purpose, and may in future be implemented in a way that does
|
||||
not fully load/download the image data.)
|
||||
|
||||
static **prefetch**(url: string): Promise
|
||||
|
||||
Prefetches a remote image for later use by downloading it.
|
||||
|
||||
## Examples
|
||||
|
||||
```js
|
||||
|
||||
@@ -38,6 +38,18 @@ which this `ScrollView` renders.
|
||||
Fires at most once per frame during scrolling. The frequency of the events can
|
||||
be contolled using the `scrollEventThrottle` prop.
|
||||
|
||||
Invoked on scroll with the following event:
|
||||
|
||||
```js
|
||||
{
|
||||
nativeEvent: {
|
||||
contentOffset: { x, y },
|
||||
contentSize: { height, width },
|
||||
layoutMeasurement: { height, width }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**refreshControl**: element
|
||||
|
||||
TODO
|
||||
@@ -51,8 +63,8 @@ When false, the content does not scroll.
|
||||
|
||||
**scrollEventThrottle**: number = 0
|
||||
|
||||
This controls how often the scroll event will be fired while scrolling (in
|
||||
events per seconds). A higher number yields better accuracy for code that is
|
||||
This controls how often the scroll event will be fired while scrolling (as a
|
||||
time interval in ms). A lower number yields better accuracy for code that is
|
||||
tracking the scroll position, but can lead to scroll performance problems. The
|
||||
default value is `0`, which means the scroll event will be sent only once each
|
||||
time the view is scrolled.
|
||||
@@ -104,7 +116,7 @@ export default class ScrollViewExample extends Component {
|
||||
contentContainerStyle={styles.container}
|
||||
horizontal
|
||||
onScroll={(e) => this.onScroll(e)}
|
||||
scrollEventThrottle={60}
|
||||
scrollEventThrottle={100}
|
||||
style={styles.root}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -6,12 +6,10 @@ such as auto-complete, auto-focus, placeholder text, and event callbacks.
|
||||
Note: some props are exclusive to or excluded from `multiline`.
|
||||
|
||||
Unsupported React Native props:
|
||||
`autoCapitalize`,
|
||||
`autoCorrect`,
|
||||
`onEndEditing`,
|
||||
`onSubmitEditing`,
|
||||
`clearButtonMode` (ios),
|
||||
`enablesReturnKeyAutomatically` (ios),
|
||||
`placeholderTextColor`,
|
||||
`returnKeyType` (ios),
|
||||
`selectionState` (ios),
|
||||
`underlineColorAndroid` (android)
|
||||
@@ -128,10 +126,6 @@ Callback that is called when the keyboard's submit button is pressed.
|
||||
The string that will be rendered in an empty `TextInput` before text has been
|
||||
entered.
|
||||
|
||||
**placeholderTextColor**: string
|
||||
|
||||
The text color of the placeholder string.
|
||||
|
||||
**secureTextEntry**: bool = false
|
||||
|
||||
If true, the text input obscures the text entered so that sensitive text like
|
||||
|
||||
@@ -99,6 +99,14 @@ from `style`.
|
||||
+ `alignContent`
|
||||
+ `alignItems`
|
||||
+ `alignSelf`
|
||||
+ `animationDelay`
|
||||
+ `animationDirection`
|
||||
+ `animationDuration`
|
||||
+ `animationFillMode`
|
||||
+ `animationIterationCount`
|
||||
+ `animationName`
|
||||
+ `animationPlayState`
|
||||
+ `animationTimingFunction`
|
||||
+ `backfaceVisibility`
|
||||
+ `backgroundAttachment`
|
||||
+ `backgroundClip`
|
||||
@@ -164,10 +172,17 @@ from `style`.
|
||||
+ `paddingRight`‡
|
||||
+ `paddingTop`
|
||||
+ `paddingVertical`
|
||||
+ `perspective`
|
||||
+ `perspectiveOrigin`
|
||||
+ `position`
|
||||
+ `right`‡
|
||||
+ `top`
|
||||
+ `transform`
|
||||
+ `transformOrigin`
|
||||
+ `transitionDelay`
|
||||
+ `transitionDuration`
|
||||
+ `transitionProperty`
|
||||
+ `transitionTimingFunction`
|
||||
+ `userSelect`
|
||||
+ `visibility`
|
||||
+ `width`
|
||||
|
||||
@@ -4,7 +4,7 @@ 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 when frequent re-rendering creates a performance bottleneck. Direct
|
||||
manipulation will not be a tool that you reach for frequently.
|
||||
|
||||
## `setNativeProps` and `shouldComponentUpdate`
|
||||
|
||||
@@ -27,14 +27,48 @@ the `url-loader` to the webpack config:
|
||||
module.exports = {
|
||||
// ...
|
||||
module: {
|
||||
loaders: {
|
||||
test: /\.(gif|jpe?g|png|svg)$/,
|
||||
loader: 'url-loader',
|
||||
query: { name: '[name].[hash:16].[ext]' }
|
||||
}
|
||||
loaders: [
|
||||
{
|
||||
test: /\.(gif|jpe?g|png|svg)$/,
|
||||
loader: 'url-loader',
|
||||
query: { name: '[name].[hash:16].[ext]' }
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
Many OSS React Native packages are not compiled to ES5 before being published.
|
||||
This can result in webpack build errors. To avoid this issue you should
|
||||
configure webpack (or your bundler of choice) to run
|
||||
`babel-preset-react-native` over the necessary `node_module`. For example:
|
||||
|
||||
```js
|
||||
// webpack.config.js
|
||||
|
||||
module.exports = {
|
||||
// ...
|
||||
module: {
|
||||
loaders: [
|
||||
{
|
||||
// transpile to ES5
|
||||
test: /\.jsx?$/,
|
||||
include: [
|
||||
path.resolve(__dirname, 'src'),
|
||||
path.resolve(__dirname, 'node_modules/react-native-something')
|
||||
],
|
||||
loader: 'babel-loader',
|
||||
query: { cacheDirectory: true }
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Please refer to the webpack documentation for more information.
|
||||
|
||||
## Web-specific code
|
||||
|
||||
Minor platform differences can use the `Platform` module.
|
||||
@@ -52,7 +86,19 @@ if (Platform.OS === 'web') {
|
||||
```
|
||||
|
||||
More substantial Web-specific implementation code should be written in files
|
||||
with the extension `.web.js`, which webpack will automatically resolve.
|
||||
with the extension `.web.js`. Webpack@1 will automatically resolve these files.
|
||||
Webpack@2 requires additional configuration.
|
||||
|
||||
```js
|
||||
// webpack.config.js
|
||||
|
||||
module.exports = {
|
||||
// ...
|
||||
resolve: {
|
||||
extensions: [ '.web.js', '.js' ]
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Optimizations
|
||||
|
||||
|
||||
@@ -16,8 +16,9 @@ module.exports = {
|
||||
}
|
||||
```
|
||||
|
||||
The `react-native-web` package also includes a `core` module that exports only
|
||||
`ReactNative`, `Image`, `StyleSheet`, `Text`, `TextInput`, and `View`.
|
||||
The `react-native-web` package also includes a `core` module that exports a
|
||||
subset of modules: `ReactNative`, `I18nManager`, `Platform`, `StyleSheet`,
|
||||
`Image`, `Text`, `TextInput`, `Touchable`, and `View`.
|
||||
|
||||
```js
|
||||
// webpack.config.js
|
||||
@@ -43,12 +44,7 @@ import ReactNative from 'react-native'
|
||||
// component that renders the app
|
||||
const AppHeaderContainer = (props) => { /* ... */ }
|
||||
|
||||
// DOM render
|
||||
ReactNative.render(<AppHeaderContainer />, document.getElementById('react-app-header'))
|
||||
|
||||
// Server render
|
||||
ReactNative.renderToString(<AppHeaderContainer />)
|
||||
ReactNative.renderToStaticMarkup(<AppHeaderContainer />)
|
||||
```
|
||||
|
||||
Rendering using the `AppRegistry`:
|
||||
@@ -63,12 +59,31 @@ const AppContainer = (props) => { /* ... */ }
|
||||
// register the app
|
||||
AppRegistry.registerComponent('App', () => AppContainer)
|
||||
|
||||
// DOM render
|
||||
AppRegistry.runApplication('App', {
|
||||
initialProps: {},
|
||||
rootTag: document.getElementById('react-app')
|
||||
})
|
||||
```
|
||||
|
||||
Setting `process.env.__REACT_NATIVE_DEBUG_ENABLED__` to `true` will expose some
|
||||
debugging logs. This can help track down when you're rendering without the
|
||||
performance benefit of cached styles.
|
||||
|
||||
## Server-side rendering
|
||||
|
||||
Rendering using the `AppRegistry`:
|
||||
|
||||
```js
|
||||
import ReactDOMServer from 'react-dom/server'
|
||||
import ReactNative, { AppRegistry } from 'react-native'
|
||||
|
||||
// component that renders the app
|
||||
const AppContainer = (props) => { /* ... */ }
|
||||
|
||||
// register the app
|
||||
AppRegistry.registerComponent('App', () => AppContainer)
|
||||
|
||||
// prerender the app
|
||||
const { html, styleElement } = AppRegistry.prerenderApplication('App', { initialProps })
|
||||
const { element, stylesheet } = AppRegistry.getApplication('App', { initialProps });
|
||||
const initialHTML = ReactDOMServer.renderToString(element);
|
||||
```
|
||||
|
||||
@@ -31,10 +31,9 @@ const styles = StyleSheet.create({
|
||||
})
|
||||
```
|
||||
|
||||
Using `StyleSheet.create` is optional but provides some key advantages: styles
|
||||
are immutable in development, certain declarations are automatically converted
|
||||
to CSS rather than applied as inline styles, and styles are only created once
|
||||
for the application and not on every render.
|
||||
Using `StyleSheet.create` is optional but provides the best performance
|
||||
(`style` is resolved to CSS stylesheets). Avoid creating unregistered style
|
||||
objects.
|
||||
|
||||
The attribute names and values are a subset of CSS. See the `style`
|
||||
documentation of individual components.
|
||||
@@ -56,12 +55,6 @@ A common pattern is to conditionally add style based on a condition:
|
||||
styles.base,
|
||||
this.state.active && styles.active
|
||||
]} />
|
||||
|
||||
// or
|
||||
<View style={{
|
||||
...styles.base,
|
||||
...(this.state.active && styles.active)
|
||||
}} />
|
||||
```
|
||||
|
||||
## Composing styles
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
const path = require('path')
|
||||
const webpack = require('webpack')
|
||||
|
||||
const DEV = process.env.NODE_ENV !== 'production';
|
||||
|
||||
module.exports = {
|
||||
module: {
|
||||
loaders: [
|
||||
@@ -19,13 +21,9 @@ module.exports = {
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
|
||||
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
|
||||
'process.env.__REACT_NATIVE_DEBUG_ENABLED__': DEV
|
||||
}),
|
||||
// https://github.com/animatedjs/animated/issues/40
|
||||
new webpack.NormalModuleReplacementPlugin(
|
||||
/es6-set/,
|
||||
path.join(__dirname, '../../src/modules/polyfills/Set.js')
|
||||
),
|
||||
new webpack.optimize.OccurenceOrderPlugin()
|
||||
],
|
||||
resolve: {
|
||||
|
||||
33
examples/apis/Clipboard/ClipboardExample.js
Normal file
33
examples/apis/Clipboard/ClipboardExample.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import { Clipboard, Text, TextInput, View } from 'react-native'
|
||||
import React, { Component } from 'react';
|
||||
import { action, storiesOf } from '@kadira/storybook';
|
||||
|
||||
class ClipboardExample extends Component {
|
||||
render() {
|
||||
return (
|
||||
<View style={{ minWidth: 300 }}>
|
||||
<Text onPress={this._handleSet}>Copy to clipboard</Text>
|
||||
<TextInput
|
||||
multiline={true}
|
||||
placeholder={'Try pasting here afterwards'}
|
||||
style={{ borderWidth: 1, height: 200, marginVertical: 20 }}
|
||||
/>
|
||||
<Text onPress={this._handleGet}>(Clipboard.getString returns a Promise that always resolves to an empty string on web)</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
_handleGet() {
|
||||
Clipboard.getString().then((value) => { console.log(`Clipboard value: ${value}`) });
|
||||
}
|
||||
|
||||
_handleSet() {
|
||||
const success = Clipboard.setString('This text was copied to the clipboard by React Native');
|
||||
console.log(`Clipboard.setString success? ${success}`);
|
||||
}
|
||||
}
|
||||
|
||||
storiesOf('api: Clipboard', module)
|
||||
.add('setString', () => (
|
||||
<ClipboardExample />
|
||||
));
|
||||
@@ -1,8 +1,8 @@
|
||||
import { storiesOf } from '@kadira/storybook';
|
||||
import { I18nManager, StyleSheet, TouchableHighlight, Text, View } from 'react-native'
|
||||
import React, { Component } from 'react';
|
||||
import { storiesOf, action } from '@kadira/storybook';
|
||||
|
||||
class RTLExample extends Component {
|
||||
class I18nManagerExample extends Component {
|
||||
componentWillUnmount() {
|
||||
I18nManager.setPreferredLanguageRTL(false)
|
||||
}
|
||||
@@ -75,5 +75,5 @@ const styles = StyleSheet.create({
|
||||
|
||||
storiesOf('api: I18nManager', module)
|
||||
.add('RTL layout', () => (
|
||||
<RTLExample />
|
||||
<I18nManagerExample />
|
||||
))
|
||||
29
examples/apis/Linking/LinkingExample.js
Normal file
29
examples/apis/Linking/LinkingExample.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Linking, StyleSheet, Text, View } from 'react-native'
|
||||
import React, { Component } from 'react';
|
||||
import { storiesOf, action } from '@kadira/storybook';
|
||||
|
||||
class LinkingExample extends Component {
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<Text onPress={() => { Linking.openURL('https://mathiasbynens.github.io/rel-noopener/malicious.html'); }} style={styles.text}>
|
||||
Linking.openURL (Expect: "The previous tab is safe and intact")
|
||||
</Text>
|
||||
<Text accessibilityRole='link' href='https://mathiasbynens.github.io/rel-noopener/malicious.html' style={styles.text} target='_blank'>
|
||||
target="_blank" (Expect: "The previous tab is safe and intact")
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
text: {
|
||||
marginVertical: 10
|
||||
}
|
||||
});
|
||||
|
||||
storiesOf('api: Linking', module)
|
||||
.add('Safe linking', () => (
|
||||
<LinkingExample />
|
||||
));
|
||||
@@ -50,7 +50,8 @@ const ToggleAnimatingActivityIndicator = React.createClass({
|
||||
return (
|
||||
<ActivityIndicator
|
||||
animating={this.state.animating}
|
||||
style={[styles.centering, {height: 80}]}
|
||||
style={styles.centering}
|
||||
hidesWhenStopped={this.props.hidesWhenStopped}
|
||||
size="large"
|
||||
/>
|
||||
);
|
||||
@@ -121,7 +122,12 @@ const examples = [
|
||||
{
|
||||
title: 'Start/stop',
|
||||
render() {
|
||||
return <ToggleAnimatingActivityIndicator />;
|
||||
return (
|
||||
<View style={[styles.horizontal, styles.centering]}>
|
||||
<ToggleAnimatingActivityIndicator />
|
||||
<ToggleAnimatingActivityIndicator hidesWhenStopped={false} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -129,7 +135,7 @@ const examples = [
|
||||
render() {
|
||||
return (
|
||||
<View style={[styles.horizontal, styles.centering]}>
|
||||
<ActivityIndicator size="40" />
|
||||
<ActivityIndicator size={40} />
|
||||
<ActivityIndicator
|
||||
style={{ marginLeft: 20, transform: [ {scale: 1.5} ] }}
|
||||
size="large"
|
||||
|
||||
80
examples/components/Button/ButtonExample.js
Normal file
80
examples/components/Button/ButtonExample.js
Normal file
@@ -0,0 +1,80 @@
|
||||
import React from 'react';
|
||||
import { action, storiesOf } from '@kadira/storybook';
|
||||
import { Button, StyleSheet, View } from 'react-native';
|
||||
|
||||
const onButtonPress = action('Button has been pressed!');
|
||||
|
||||
const examples = [
|
||||
{
|
||||
title: 'Simple Button',
|
||||
description: 'The title and onPress handler are required. It is ' +
|
||||
'recommended to set accessibilityLabel to help make your app usable by ' +
|
||||
'everyone.',
|
||||
render: function() {
|
||||
return (
|
||||
<Button
|
||||
onPress={onButtonPress}
|
||||
title="Press Me"
|
||||
accessibilityLabel="See an informative alert"
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Adjusted color',
|
||||
description: 'Adjusts the color in a way that looks standard on each ' +
|
||||
'platform. On iOS, the color prop controls the color of the text. On ' +
|
||||
'Android, the color adjusts the background color of the button.',
|
||||
render: function() {
|
||||
return (
|
||||
<Button
|
||||
onPress={onButtonPress}
|
||||
title="Press Purple"
|
||||
color="#841584"
|
||||
accessibilityLabel="Learn more about purple"
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Fit to text layout',
|
||||
description: 'This layout strategy lets the title define the width of ' +
|
||||
'the button',
|
||||
render: function() {
|
||||
return (
|
||||
<View style={{flexDirection: 'row', justifyContent: 'space-between'}}>
|
||||
<Button
|
||||
onPress={onButtonPress}
|
||||
title="This looks great!"
|
||||
accessibilityLabel="This sounds great!"
|
||||
/>
|
||||
<Button
|
||||
onPress={onButtonPress}
|
||||
title="Ok!"
|
||||
color="#841584"
|
||||
accessibilityLabel="Ok, Great!"
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Disabled Button',
|
||||
description: 'All interactions for the component are disabled.',
|
||||
render: function() {
|
||||
return (
|
||||
<Button
|
||||
disabled
|
||||
onPress={onButtonPress}
|
||||
title="I Am Disabled"
|
||||
accessibilityLabel="See an informative alert"
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
examples.forEach((example) => {
|
||||
storiesOf('component: Button', module)
|
||||
.add(example.title, () => example.render());
|
||||
});
|
||||
@@ -28,10 +28,9 @@ import { ActivityIndicator, Image, Platform, StyleSheet, Text, View } from 'reac
|
||||
var base64Icon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAABLCAQAAACSR7JhAAADtUlEQVR4Ac3YA2Bj6QLH0XPT1Fzbtm29tW3btm3bfLZtv7e2ObZnms7d8Uw098tuetPzrxv8wiISrtVudrG2JXQZ4VOv+qUfmqCGGl1mqLhoA52oZlb0mrjsnhKpgeUNEs91Z0pd1kvihA3ULGVHiQO2narKSHKkEMulm9VgUyE60s1aWoMQUbpZOWE+kaqs4eLEjdIlZTcFZB0ndc1+lhB1lZrIuk5P2aib1NBpZaL+JaOGIt0ls47SKzLC7CqrlGF6RZ09HGoNy1lYl2aRSWL5GuzqWU1KafRdoRp0iOQEiDzgZPnG6DbldcomadViflnl/cL93tOoVbsOLVM2jylvdWjXolWX1hmfZbGR/wjypDjFLSZIRov09BgYmtUqPQPlQrPapecLgTIy0jMgPKtTeob2zWtrGH3xvjUkPCtNg/tm1rjwrMa+mdUkPd3hWbH0jArPGiU9ufCsNNWFZ40wpwn+62/66R2RUtoso1OB34tnLOcy7YB1fUdc9e0q3yru8PGM773vXsuZ5YIZX+5xmHwHGVvlrGPN6ZSiP1smOsMMde40wKv2VmwPPVXNut4sVpUreZiLBHi0qln/VQeI/LTMYXpsJtFiclUN+5HVZazim+Ky+7sAvxWnvjXrJFneVtLWLyPJu9K3cXLWeOlbMTlrIelbMDlrLenrjEQOtIF+fuI9xRp9ZBFp6+b6WT8RrxEpdK64BuvHgDk+vUy+b5hYk6zfyfs051gRoNO1usU12WWRWL73/MMEy9pMi9qIrR4ZpV16Rrvduxazmy1FSvuFXRkqTnE7m2kdb5U8xGjLw/spRr1uTov4uOgQE+0N/DvFrG/Jt7i/FzwxbA9kDanhf2w+t4V97G8lrT7wc08aA2QNUkuTfW/KimT01wdlfK4yEw030VfT0RtZbzjeMprNq8m8tnSTASrTLti64oBNdpmMQm0eEwvfPwRbUBywG5TzjPCsdwk3IeAXjQblLCoXnDVeoAz6SfJNk5TTzytCNZk/POtTSV40NwOFWzw86wNJRpubpXsn60NJFlHeqlYRbslqZm2jnEZ3qcSKgm0kTli3zZVS7y/iivZTweYXJ26Y+RTbV1zh3hYkgyFGSTKPfRVbRqWWVReaxYeSLarYv1Qqsmh1s95S7G+eEWK0f3jYKTbV6bOwepjfhtafsvUsqrQvrGC8YhmnO9cSCk3yuY984F1vesdHYhWJ5FvASlacshUsajFt2mUM9pqzvKGcyNJW0arTKN1GGGzQlH0tXwLDgQTurS8eIQAAAABJRU5ErkJggg==';
|
||||
|
||||
//var ImageCapInsetsExample = require('./ImageCapInsetsExample');
|
||||
//const IMAGE_PREFETCH_URL = 'http://facebook.github.io/origami/public/images/blog-hero.jpg?r=1&t=' + Date.now();
|
||||
//var prefetchTask = Image.prefetch(IMAGE_PREFETCH_URL);
|
||||
const IMAGE_PREFETCH_URL = 'http://origami.design/public/images/bird-logo.png?r=1&t=' + Date.now();
|
||||
var prefetchTask = Image.prefetch(IMAGE_PREFETCH_URL);
|
||||
|
||||
/*
|
||||
var NetworkImageCallbackExample = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
@@ -88,7 +87,6 @@ var NetworkImageCallbackExample = React.createClass({
|
||||
});
|
||||
}
|
||||
});
|
||||
*/
|
||||
|
||||
var NetworkImageExample = React.createClass({
|
||||
getInitialState: function() {
|
||||
@@ -118,7 +116,6 @@ var NetworkImageExample = React.createClass({
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
var ImageSizeExample = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
@@ -133,24 +130,25 @@ var ImageSizeExample = React.createClass({
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<View style={{flexDirection: 'row'}}>
|
||||
<Image
|
||||
style={{
|
||||
width: 60,
|
||||
height: 60,
|
||||
backgroundColor: 'transparent',
|
||||
marginRight: 10,
|
||||
}}
|
||||
source={this.props.source} />
|
||||
<View>
|
||||
<Text>
|
||||
Actual dimensions:{'\n'}
|
||||
Width: {this.state.width}, Height: {this.state.height}
|
||||
width: {this.state.width}, height: {this.state.height}
|
||||
</Text>
|
||||
<Image
|
||||
source={this.props.source}
|
||||
style={{
|
||||
backgroundColor: '#eee',
|
||||
height: 227,
|
||||
marginTop: 10,
|
||||
width: 323
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
});
|
||||
*/
|
||||
|
||||
/*
|
||||
var MultipleSourcesExample = React.createClass({
|
||||
getInitialState: function() {
|
||||
@@ -239,17 +237,17 @@ const examples = [
|
||||
);
|
||||
},
|
||||
},
|
||||
/*
|
||||
{
|
||||
title: 'Image Loading Events',
|
||||
render: function() {
|
||||
return (
|
||||
<NetworkImageCallbackExample source={{uri: 'http://facebook.github.io/origami/public/images/blog-hero.jpg?r=1&t=' + Date.now()}}
|
||||
prefetchedSource={{uri: IMAGE_PREFETCH_URL}}/>
|
||||
<NetworkImageCallbackExample
|
||||
source={{uri: 'http://origami.design/public/images/bird-logo.png?r=1&t=' + Date.now()}}
|
||||
prefetchedSource={{uri: IMAGE_PREFETCH_URL}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
*/
|
||||
{
|
||||
title: 'Error Handler',
|
||||
render: function() {
|
||||
@@ -263,7 +261,7 @@ const examples = [
|
||||
title: 'Image Download Progress',
|
||||
render: function() {
|
||||
return (
|
||||
<NetworkImageExample source={{uri: 'http://facebook.github.io/origami/public/images/blog-hero.jpg?r=1'}}/>
|
||||
<NetworkImageExample source={{uri: 'http://origami.design/public/images/bird-logo.png?r=1'}}/>
|
||||
);
|
||||
},
|
||||
platform: 'ios',
|
||||
@@ -567,14 +565,12 @@ const examples = [
|
||||
platform: 'ios',
|
||||
},
|
||||
*/
|
||||
/*
|
||||
{
|
||||
title: 'Image Size',
|
||||
render: function() {
|
||||
return <ImageSizeExample source={fullImage} />;
|
||||
return <ImageSizeExample source={{ uri: 'https://upload.wikimedia.org/wikipedia/commons/d/d7/Chestnut-mandibled_Toucan.jpg' }} />;
|
||||
},
|
||||
},
|
||||
*/
|
||||
/*
|
||||
{
|
||||
title: 'MultipleSourcesExample',
|
||||
|
||||
@@ -1,4 +1,80 @@
|
||||
import React from 'react';
|
||||
import { storiesOf, action } from '@kadira/storybook';
|
||||
import { ListView } from 'react-native'
|
||||
import { storiesOf } from '@kadira/storybook';
|
||||
import { ListView, StyleSheet, Text, View } from 'react-native';
|
||||
|
||||
const generateData = (length) => Array.from({ length }).map((item, i) => i);
|
||||
const dataSource = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
|
||||
|
||||
storiesOf('component: ListView', module)
|
||||
.add('vertical', () => (
|
||||
<View style={styles.scrollViewContainer}>
|
||||
<ListView
|
||||
contentContainerStyle={styles.scrollViewContentContainerStyle}
|
||||
dataSource={dataSource.cloneWithRows(generateData(100))}
|
||||
initialListSize={100}
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onScroll={(e) => { console.log('ScrollView.onScroll', e); } }
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
renderRow={(row) => (
|
||||
<View><Text>{row}</Text></View>
|
||||
)}
|
||||
scrollEventThrottle={1000} // 1 event per second
|
||||
style={styles.scrollViewStyle}
|
||||
/>
|
||||
</View>
|
||||
))
|
||||
.add('incremental rendering - large pageSize', () => (
|
||||
<View style={styles.scrollViewContainer}>
|
||||
<ListView
|
||||
contentContainerStyle={styles.scrollViewContentContainerStyle}
|
||||
dataSource={dataSource.cloneWithRows(generateData(5000))}
|
||||
initialListSize={100}
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onScroll={(e) => { console.log('ScrollView.onScroll', e); } }
|
||||
pageSize={50}
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
renderRow={(row) => (
|
||||
<View><Text>{row}</Text></View>
|
||||
)}
|
||||
scrollEventThrottle={1000} // 1 event per second
|
||||
style={styles.scrollViewStyle}
|
||||
/>
|
||||
</View>
|
||||
))
|
||||
.add('incremental rendering - small pageSize', () => (
|
||||
<View style={styles.scrollViewContainer}>
|
||||
<ListView
|
||||
contentContainerStyle={styles.scrollViewContentContainerStyle}
|
||||
dataSource={dataSource.cloneWithRows(generateData(5000))}
|
||||
initialListSize={5}
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onScroll={(e) => { console.log('ScrollView.onScroll', e); } }
|
||||
pageSize={1}
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
renderRow={(row) => (
|
||||
<View><Text>{row}</Text></View>
|
||||
)}
|
||||
scrollEventThrottle={1000} // 1 event per second
|
||||
style={styles.scrollViewStyle}
|
||||
/>
|
||||
</View>
|
||||
));
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
box: {
|
||||
flexGrow: 1,
|
||||
justifyContent: 'center',
|
||||
borderWidth: 1
|
||||
},
|
||||
scrollViewContainer: {
|
||||
height: '200px',
|
||||
width: 300
|
||||
},
|
||||
scrollViewStyle: {
|
||||
borderWidth: '1px'
|
||||
},
|
||||
scrollViewContentContainerStyle: {
|
||||
backgroundColor: '#eee',
|
||||
padding: '10px'
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import React from 'react';
|
||||
import { storiesOf, action } from '@kadira/storybook';
|
||||
import { action, storiesOf } from '@kadira/storybook';
|
||||
import { ScrollView, StyleSheet, Text, TouchableHighlight, View } from 'react-native'
|
||||
|
||||
const onScroll = action('ScrollView.onScroll');
|
||||
|
||||
storiesOf('component: ScrollView', module)
|
||||
.add('vertical', () => (
|
||||
<View style={styles.scrollViewContainer}>
|
||||
<ScrollView
|
||||
contentContainerStyle={styles.scrollViewContentContainerStyle}
|
||||
onScroll={e => console.log('ScrollView.onScroll', e)}
|
||||
scrollEventThrottle={1} // 1 event per second
|
||||
onScroll={onScroll}
|
||||
scrollEventThrottle={1000} // 1 event per second
|
||||
style={styles.scrollViewStyle}
|
||||
>
|
||||
{Array.from({ length: 50 }).map((item, i) => (
|
||||
@@ -24,8 +26,8 @@ storiesOf('component: ScrollView', module)
|
||||
<ScrollView
|
||||
contentContainerStyle={styles.scrollViewContentContainerStyle}
|
||||
horizontal
|
||||
onScroll={e => console.log('ScrollView.onScroll', e)}
|
||||
scrollEventThrottle={1} // 1 event per second
|
||||
onScroll={onScroll}
|
||||
scrollEventThrottle={16} // ~60 events per second
|
||||
style={styles.scrollViewStyle}
|
||||
>
|
||||
{Array.from({ length: 50 }).map((item, i) => (
|
||||
@@ -48,10 +50,10 @@ const styles = StyleSheet.create({
|
||||
width: 300
|
||||
},
|
||||
scrollViewStyle: {
|
||||
borderWidth: '1px'
|
||||
borderWidth: 1
|
||||
},
|
||||
scrollViewContentContainerStyle: {
|
||||
backgroundColor: '#eee',
|
||||
padding: '10px'
|
||||
padding: 10
|
||||
}
|
||||
})
|
||||
|
||||
@@ -79,7 +79,7 @@ const examples = [
|
||||
title: 'Wrap',
|
||||
render: function() {
|
||||
return (
|
||||
<Text>
|
||||
<Text style={{ WebkitFontSmoothing: 'antialiased' }}>
|
||||
The text should wrap if it goes on multiple lines. See, this is going to
|
||||
the next line.
|
||||
</Text>
|
||||
|
||||
@@ -59,7 +59,7 @@ class TextEventsExample extends React.Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<View style={{ alignItems: 'center' }}>
|
||||
<TextInput
|
||||
autoCapitalize="none"
|
||||
placeholder="Enter text to see events"
|
||||
@@ -83,7 +83,7 @@ class TextEventsExample extends React.Component {
|
||||
onKeyPress={(event) => {
|
||||
this.updateText('onKeyPress key: ' + event.nativeEvent.key);
|
||||
}}
|
||||
style={styles.default}
|
||||
style={[ styles.default, { maxWidth: 200 } ]}
|
||||
/>
|
||||
<Text style={styles.eventLabel}>
|
||||
{this.state.curText}{'\n'}
|
||||
@@ -210,25 +210,16 @@ class TokenizedTextExample extends React.Component {
|
||||
}
|
||||
parts.push(_text);
|
||||
|
||||
//highlight hashtags
|
||||
parts = parts.map((text) => {
|
||||
if (/^#/.test(text)) {
|
||||
return <Text key={text} style={styles.hashtag}>{text}</Text>;
|
||||
} else {
|
||||
return text;
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<View>
|
||||
<TextInput
|
||||
value={parts.join('')}
|
||||
multiline={true}
|
||||
style={styles.multiline}
|
||||
onChangeText={(text) => {
|
||||
this.setState({text});
|
||||
}}>
|
||||
<Text>{parts}</Text>
|
||||
</TextInput>
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -279,7 +270,7 @@ class BlurOnSubmitExample extends React.Component {
|
||||
<TextInput
|
||||
ref="5"
|
||||
style={styles.default}
|
||||
keyboardType="numbers-and-punctuation"
|
||||
keyboardType="numeric"
|
||||
placeholder="blurOnSubmit = true"
|
||||
returnKeyType="done"
|
||||
/>
|
||||
@@ -519,15 +510,15 @@ const examples = [
|
||||
render: function() {
|
||||
var keyboardTypes = [
|
||||
'default',
|
||||
'ascii-capable',
|
||||
'numbers-and-punctuation',
|
||||
//'ascii-capable',
|
||||
//'numbers-and-punctuation',
|
||||
'url',
|
||||
'number-pad',
|
||||
'phone-pad',
|
||||
'name-phone-pad',
|
||||
//'name-phone-pad',
|
||||
'email-address',
|
||||
'decimal-pad',
|
||||
'twitter',
|
||||
//'decimal-pad',
|
||||
//'twitter',
|
||||
'web-search',
|
||||
'numeric',
|
||||
];
|
||||
@@ -776,14 +767,6 @@ const examples = [
|
||||
style={styles.multiline}
|
||||
dataDetectorTypes="phoneNumber"
|
||||
/>
|
||||
<TextInput
|
||||
placeholder="multiline with children"
|
||||
multiline={true}
|
||||
enablesReturnKeyAutomatically={true}
|
||||
returnKeyType="go"
|
||||
style={styles.multiline}>
|
||||
<View style={styles.multilineChild}/>
|
||||
</TextInput>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -307,11 +307,11 @@ var TouchableDisabled = React.createClass({
|
||||
render: function() {
|
||||
return (
|
||||
<View>
|
||||
<TouchableOpacity disabled={true} style={[styles.row, styles.block]}>
|
||||
<TouchableOpacity disabled={true} style={[styles.row, styles.block]} onPress={action('TouchableOpacity')}>
|
||||
<Text style={styles.disabledButton}>Disabled TouchableOpacity</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity disabled={false} style={[styles.row, styles.block]}>
|
||||
<TouchableOpacity disabled={false} style={[styles.row, styles.block]} onPress={action('TouchableOpacity')}>
|
||||
<Text style={styles.button}>Enabled TouchableOpacity</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
@@ -321,7 +321,7 @@ var TouchableDisabled = React.createClass({
|
||||
animationVelocity={0}
|
||||
underlayColor="rgb(210, 230, 255)"
|
||||
style={[styles.row, styles.block]}
|
||||
onPress={() => console.log('custom THW text - highlight')}>
|
||||
onPress={action('TouchableHighlight')}>
|
||||
<Text style={styles.disabledButton}>
|
||||
Disabled TouchableHighlight
|
||||
</Text>
|
||||
@@ -332,7 +332,7 @@ var TouchableDisabled = React.createClass({
|
||||
animationVelocity={0}
|
||||
underlayColor="rgb(210, 230, 255)"
|
||||
style={[styles.row, styles.block]}
|
||||
onPress={() => console.log('custom THW text - highlight')}>
|
||||
onPress={action('TouchableHighlight')}>
|
||||
<Text style={styles.button}>
|
||||
Enabled TouchableHighlight
|
||||
</Text>
|
||||
|
||||
@@ -31,6 +31,17 @@ var styles = StyleSheet.create({
|
||||
borderColor: '#000033',
|
||||
borderWidth: 1,
|
||||
},
|
||||
shadowBox: {
|
||||
width: 100,
|
||||
height: 100,
|
||||
borderWidth: 2,
|
||||
},
|
||||
shadow: {
|
||||
shadowOpacity: 0.5,
|
||||
shadowColor: 'red',
|
||||
shadowRadius: 3,
|
||||
shadowOffset: { width: 3, height: 3 },
|
||||
},
|
||||
zIndex: {
|
||||
justifyContent: 'space-around',
|
||||
width: 100,
|
||||
@@ -242,6 +253,19 @@ const examples = [
|
||||
return <ZIndexExample />;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Basic shadow',
|
||||
render() {
|
||||
return <View style={[ styles.shadowBox, styles.shadow ]} />;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Shaped shadow',
|
||||
description: 'borderRadius: 50',
|
||||
render() {
|
||||
return <View style={[ styles.shadowBox, styles.shadow, {borderRadius: 50} ]} />;
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
examples.forEach((example) => {
|
||||
|
||||
@@ -23,7 +23,7 @@ var {
|
||||
AppRegistry,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableBounce,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} = ReactNative;
|
||||
|
||||
@@ -139,9 +139,9 @@ class GameEndOverlay extends React.Component {
|
||||
return (
|
||||
<View style={styles.overlay}>
|
||||
<Text style={styles.overlayMessage}>{message}</Text>
|
||||
<TouchableBounce onPress={this.props.onRestart} style={styles.tryAgain}>
|
||||
<TouchableOpacity onPress={this.props.onRestart} style={styles.tryAgain}>
|
||||
<Text style={styles.tryAgainText}>Try Again?</Text>
|
||||
</TouchableBounce>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ var Cell = React.createClass({
|
||||
case 2:
|
||||
return styles.cellTextO;
|
||||
default:
|
||||
return {};
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
53
package.json
53
package.json
@@ -1,40 +1,49 @@
|
||||
{
|
||||
"name": "react-native-web",
|
||||
"version": "0.0.51",
|
||||
"version": "0.0.70",
|
||||
"description": "React Native for Web",
|
||||
"main": "dist/index.js",
|
||||
"files": [
|
||||
"dist"
|
||||
"dist",
|
||||
"src",
|
||||
"!**/__tests__"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "del ./dist && mkdir dist && babel src -d dist --ignore **/__tests__",
|
||||
"build:examples": "build-storybook -o dist-examples -c ./examples/.storybook",
|
||||
"build:performance": "cd performance && webpack",
|
||||
"build:umd": "webpack --config webpack.config.js --sort-assets-by --progress",
|
||||
"deploy:examples": "git checkout gh-pages && rm -rf ./storybook && mv dist-examples storybook && git add -A && git commit -m \"Storybook deploy\" && git push origin gh-pages && git checkout -",
|
||||
"examples": "start-storybook -p 9001 -c ./examples/.storybook --dont-track",
|
||||
"lint": "eslint src",
|
||||
"lint": "eslint performance src",
|
||||
"prepublish": "npm run build && npm run build:umd",
|
||||
"test": "jest",
|
||||
"test:watch": "npm run test -- --watch"
|
||||
"test": "npm run lint && npm run test:jest",
|
||||
"test:jest": "jest",
|
||||
"test:watch": "npm run test:jest -- --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"animated": "^0.1.3",
|
||||
"babel-runtime": "^6.11.6",
|
||||
"fbjs": "^0.8.4",
|
||||
"inline-style-prefixer": "^2.0.1",
|
||||
"lodash": "^4.15.0",
|
||||
"react-dom": "~15.3.2",
|
||||
"animated": "^0.1.5",
|
||||
"array-find-index": "^1.0.2",
|
||||
"asap": "^2.0.5",
|
||||
"babel-runtime": "^6.20.0",
|
||||
"debounce": "^1.0.0",
|
||||
"deep-assign": "^2.0.0",
|
||||
"fbjs": "^0.8.8",
|
||||
"inline-style-prefixer": "^2.0.5",
|
||||
"normalize-css-color": "^1.0.2",
|
||||
"react-dom": "~15.4.1",
|
||||
"react-textarea-autosize": "^4.0.4",
|
||||
"react-timer-mixin": "^0.13.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@kadira/storybook": "^2.5.1",
|
||||
"babel-cli": "^6.14.0",
|
||||
"babel-core": "^6.14.0",
|
||||
"babel-eslint": "^6.1.2",
|
||||
"babel-loader": "^6.2.5",
|
||||
"babel-preset-react-native": "^1.9.0",
|
||||
"del-cli": "^0.2.0",
|
||||
"babel-core": "^6.21.0",
|
||||
"babel-eslint": "^7.1.1",
|
||||
"babel-loader": "^6.2.10",
|
||||
"babel-plugin-transform-react-remove-prop-types": "^0.3.0",
|
||||
"babel-preset-react-native": "^1.9.1",
|
||||
"del-cli": "^0.2.1",
|
||||
"enzyme": "^2.4.1",
|
||||
"eslint": "^3.4.0",
|
||||
"eslint-plugin-jsx-a11y": "^2.2.0",
|
||||
@@ -42,15 +51,17 @@
|
||||
"eslint-plugin-react": "^6.1.2",
|
||||
"file-loader": "^0.9.0",
|
||||
"jest": "^16.0.2",
|
||||
"marky": "^1.1.1",
|
||||
"node-libs-browser": "^0.5.3",
|
||||
"react": "~15.3.2",
|
||||
"react-addons-test-utils": "~15.3.2",
|
||||
"react-test-renderer": "~15.3.2",
|
||||
"react": "~15.4.1",
|
||||
"react-addons-test-utils": "~15.4.1",
|
||||
"react-test-renderer": "~15.4.1",
|
||||
"url-loader": "^0.5.7",
|
||||
"webpack": "^1.13.2"
|
||||
"webpack": "^1.13.2",
|
||||
"webpack-bundle-analyzer": "^2.2.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "~15.3.2"
|
||||
"react": "~15.4.1"
|
||||
},
|
||||
"author": "Nicolas Gallagher",
|
||||
"license": "BSD-3-Clause",
|
||||
|
||||
65
performance/benchmark.js
Normal file
65
performance/benchmark.js
Normal file
@@ -0,0 +1,65 @@
|
||||
import * as marky from 'marky';
|
||||
|
||||
const fmt = (time) => `${Math.round(time * 100) / 100}ms`;
|
||||
|
||||
const measure = (name, fn) => {
|
||||
marky.mark(name);
|
||||
fn();
|
||||
const performanceMeasure = marky.stop(name);
|
||||
return performanceMeasure;
|
||||
};
|
||||
|
||||
const benchmark = ({ name, description, setup, teardown, task, runs }) => {
|
||||
return new Promise((resolve) => {
|
||||
const performanceMeasures = [];
|
||||
let i = 0;
|
||||
|
||||
setup();
|
||||
const first = measure('first', task);
|
||||
teardown();
|
||||
|
||||
const done = () => {
|
||||
const mean = performanceMeasures.reduce((sum, performanceMeasure) => {
|
||||
return sum + performanceMeasure.duration;
|
||||
}, 0) / runs;
|
||||
|
||||
const firstDuration = fmt(first.duration);
|
||||
const meanDuration = fmt(mean);
|
||||
|
||||
console.log(`First: ${firstDuration}`);
|
||||
console.log(`Mean: ${meanDuration}`);
|
||||
console.groupEnd();
|
||||
resolve(mean);
|
||||
};
|
||||
|
||||
const a = () => {
|
||||
setup();
|
||||
window.requestAnimationFrame(b);
|
||||
};
|
||||
|
||||
const b = () => {
|
||||
performanceMeasures.push(measure('mean', task));
|
||||
window.requestAnimationFrame(c);
|
||||
};
|
||||
|
||||
const c = () => {
|
||||
teardown();
|
||||
window.requestAnimationFrame(d);
|
||||
};
|
||||
|
||||
const d = () => {
|
||||
i += 1;
|
||||
if (i < runs) {
|
||||
window.requestAnimationFrame(a);
|
||||
} else {
|
||||
window.requestAnimationFrame(done);
|
||||
}
|
||||
};
|
||||
|
||||
console.group(`[benchmark] ${name}`);
|
||||
console.log(description);
|
||||
window.requestAnimationFrame(a);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = benchmark;
|
||||
88
performance/benchmarks/deepTree/createDeepTree.js
Normal file
88
performance/benchmarks/deepTree/createDeepTree.js
Normal file
@@ -0,0 +1,88 @@
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
|
||||
const createDeepTree = ({ StyleSheet, View }, options = {}) => {
|
||||
class DeepTree extends Component {
|
||||
static propTypes = {
|
||||
breadth: PropTypes.number.isRequired,
|
||||
depth: PropTypes.number.isRequired,
|
||||
id: PropTypes.number.isRequired,
|
||||
wrap: PropTypes.number.isRequired
|
||||
};
|
||||
|
||||
render() {
|
||||
const { breadth, depth, id, wrap } = this.props;
|
||||
|
||||
let result = (
|
||||
<View
|
||||
style={[
|
||||
styles.outer,
|
||||
depth % 2 === 0 ? styles.even : styles.odd,
|
||||
styles[`custom${id % 3}`]
|
||||
]}
|
||||
>
|
||||
{depth === 0 && (
|
||||
<View
|
||||
style={[
|
||||
styles.terminal,
|
||||
styles[`terminal${id % 3}`]
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
{depth !== 0 && Array.from({ length: breadth }).map((el, i) => (
|
||||
<DeepTree
|
||||
breadth={breadth}
|
||||
depth={depth - 1}
|
||||
id={i}
|
||||
key={i}
|
||||
wrap={wrap}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
);
|
||||
for (let i = 0; i < wrap; i++) {
|
||||
result = <View>{result}</View>;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
const stylesObject = {
|
||||
outer: {
|
||||
padding: 4
|
||||
},
|
||||
odd: {
|
||||
flexDirection: 'row'
|
||||
},
|
||||
even: {
|
||||
flexDirection: 'column'
|
||||
},
|
||||
custom0: {
|
||||
backgroundColor: '#222'
|
||||
},
|
||||
custom1: {
|
||||
backgroundColor: '#666'
|
||||
},
|
||||
custom2: {
|
||||
backgroundColor: '#999'
|
||||
},
|
||||
terminal: {
|
||||
width: 20,
|
||||
height: 20
|
||||
},
|
||||
terminal0: {
|
||||
backgroundColor: 'blue'
|
||||
},
|
||||
terminal1: {
|
||||
backgroundColor: 'orange'
|
||||
},
|
||||
terminal2: {
|
||||
backgroundColor: 'red'
|
||||
}
|
||||
};
|
||||
|
||||
const styles = options.registerStyles ? StyleSheet.create(stylesObject) : stylesObject;
|
||||
|
||||
return DeepTree;
|
||||
};
|
||||
|
||||
module.exports = createDeepTree;
|
||||
24
performance/benchmarks/deepTree/index.js
Normal file
24
performance/benchmarks/deepTree/index.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import benchmark from '../../benchmark';
|
||||
import createDeepTree from './createDeepTree';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import ReactNative from 'react-native';
|
||||
|
||||
const deepTreeBenchmark = ({ breadth, depth, registerStyles = true, runs, wrap }, node) => () => {
|
||||
// React Native for Web implementation of the tree
|
||||
const DeepTree = createDeepTree(ReactNative, { registerStyles });
|
||||
|
||||
const setup = () => {};
|
||||
const teardown = () => ReactDOM.unmountComponentAtNode(node);
|
||||
|
||||
return benchmark({
|
||||
name: `DeepTree (${registerStyles ? 'registered' : 'unregistered'}) styles)`,
|
||||
description: `depth=${depth}, breadth=${breadth}, wrap=${wrap}`,
|
||||
runs,
|
||||
setup,
|
||||
teardown,
|
||||
task: () => ReactDOM.render(<DeepTree breadth={breadth} depth={depth} id={0} wrap={wrap} />, node)
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = deepTreeBenchmark;
|
||||
11
performance/index.html
Normal file
11
performance/index.html
Normal file
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Title</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="root"></div>
|
||||
<script src="../dist-performance/performance.bundle.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
16
performance/index.js
Normal file
16
performance/index.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import createDeepTree from './benchmarks/deepTree/createDeepTree';
|
||||
import deepTree from './benchmarks/deepTree';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import ReactNative from 'react-native';
|
||||
|
||||
const node = document.querySelector('.root');
|
||||
const DeepTree = createDeepTree(ReactNative);
|
||||
|
||||
Promise.resolve()
|
||||
.then(deepTree({ wrap: 4, depth: 3, breadth: 10, runs: 10, registerStyles: false }, node))
|
||||
.then(deepTree({ wrap: 1, depth: 5, breadth: 3, runs: 20, registerStyles: false }, node))
|
||||
.then(deepTree({ wrap: 4, depth: 3, breadth: 10, runs: 10 }, node))
|
||||
.then(deepTree({ wrap: 1, depth: 5, breadth: 3, runs: 20 }, node))
|
||||
.then(() => ReactDOM.render(<DeepTree breadth={3} depth={5} id={0} wrap={1} />, node));
|
||||
|
||||
45
performance/webpack.config.js
Normal file
45
performance/webpack.config.js
Normal file
@@ -0,0 +1,45 @@
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
|
||||
|
||||
module.exports = {
|
||||
entry: {
|
||||
performance: './index'
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, '../dist-performance'),
|
||||
filename: 'performance.bundle.js'
|
||||
},
|
||||
module: {
|
||||
loaders: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'babel-loader',
|
||||
query: { cacheDirectory: true }
|
||||
}
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('production') }),
|
||||
new webpack.optimize.DedupePlugin(),
|
||||
// https://github.com/animatedjs/animated/issues/40
|
||||
new webpack.NormalModuleReplacementPlugin(
|
||||
/es6-set/,
|
||||
path.join(__dirname, '../src/modules/polyfills/Set.js')
|
||||
),
|
||||
new webpack.optimize.OccurenceOrderPlugin(),
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
compress: {
|
||||
dead_code: true,
|
||||
screw_ie8: true,
|
||||
warnings: true
|
||||
}
|
||||
})
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'react-native': path.join(__dirname, '../src')
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,5 +1,6 @@
|
||||
import Animated from 'animated';
|
||||
import Image from '../../components/Image';
|
||||
import ScrollView from '../../components/ScrollView';
|
||||
import StyleSheet from '../StyleSheet';
|
||||
import Text from '../../components/Text';
|
||||
import View from '../../components/View';
|
||||
@@ -9,6 +10,7 @@ Animated.inject.FlattenStyle(StyleSheet.flatten);
|
||||
module.exports = {
|
||||
...Animated,
|
||||
Image: Animated.createAnimatedComponent(Image),
|
||||
ScrollView: Animated.createAnimatedComponent(ScrollView),
|
||||
Text: Animated.createAnimatedComponent(Text),
|
||||
View: Animated.createAnimatedComponent(View)
|
||||
};
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
exports[`apis/AppRegistry/renderApplication getApplication 1`] = `
|
||||
"<style id=\"react-native-stylesheet\">
|
||||
/* React Native StyleSheet*/
|
||||
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::-webkit-inner-spin-button,input::-webkit-outer-spin-button,input::-webkit-search-cancel-button,input::-webkit-search-decoration,input::-webkit-search-results-button,input::-webkit-search-results-decoration{display:none}
|
||||
@keyframes rn-ActivityIndicator-animation{0%{-webkit-transform: rotate(0deg); transform: rotate(0deg);}100%{-webkit-transform: rotate(360deg); transform: rotate(360deg);}}
|
||||
@keyframes rn-ProgressBar-animation{0%{-webkit-transform: translateX(-100%); transform: translateX(-100%);}100%{-webkit-transform: translateX(400%); transform: translateX(400%);}}
|
||||
.rn-pointerEvents\\:auto,.rn-pointerEvents\\:box-only,.rn-pointerEvents\\:box-none *{pointer-events:auto}.rn-pointerEvents\\:none,.rn-pointerEvents\\:box-only *,.rn-pointerEvents\\:box-none{pointer-events:none}
|
||||
.rn-bottom\\:0px{bottom:0px}
|
||||
.rn-left\\:0px{left:0px}
|
||||
.rn-position\\:absolute{position:absolute}
|
||||
.rn-right\\:0px{right:0px}
|
||||
.rn-top\\:0px{top:0px}
|
||||
.rn-alignItems\\:stretch{-ms-flex-align:stretch;-webkit-align-items:stretch;-webkit-box-align:stretch;align-items:stretch}
|
||||
.rn-backgroundColor\\:transparent{background-color:transparent}
|
||||
.rn-borderTopStyle\\:solid{border-top-style:solid}
|
||||
.rn-borderRightStyle\\:solid{border-right-style:solid}
|
||||
.rn-borderBottomStyle\\:solid{border-bottom-style:solid}
|
||||
.rn-borderLeftStyle\\:solid{border-left-style:solid}
|
||||
.rn-borderTopWidth\\:0px{border-top-width:0px}
|
||||
.rn-borderRightWidth\\:0px{border-right-width:0px}
|
||||
.rn-borderBottomWidth\\:0px{border-bottom-width:0px}
|
||||
.rn-borderLeftWidth\\:0px{border-left-width:0px}
|
||||
.rn-boxSizing\\:border-box{-moz-box-sizing:border-box;box-sizing:border-box}
|
||||
.rn-color\\:inherit{color:inherit}
|
||||
.rn-display\\:flex{display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex}
|
||||
.rn-flexBasis\\:auto{-ms-preferred-size:auto;-webkit-flex-basis:auto;flex-basis:auto}
|
||||
.rn-flexDirection\\:column{-ms-flex-direction:column;-webkit-box-direction:normal;-webkit-box-orient:vertical;-webkit-flex-direction:column;flex-direction:column}
|
||||
.rn-font\\:inherit{font:inherit}
|
||||
.rn-listStyle\\:none{list-style:none}
|
||||
.rn-marginTop\\:0px{margin-top:0px}
|
||||
.rn-marginRight\\:0px{margin-right:0px}
|
||||
.rn-marginBottom\\:0px{margin-bottom:0px}
|
||||
.rn-marginLeft\\:0px{margin-left:0px}
|
||||
.rn-minHeight\\:0px{min-height:0px}
|
||||
.rn-minWidth\\:0px{min-width:0px}
|
||||
.rn-paddingTop\\:0px{padding-top:0px}
|
||||
.rn-paddingRight\\:0px{padding-right:0px}
|
||||
.rn-paddingBottom\\:0px{padding-bottom:0px}
|
||||
.rn-paddingLeft\\:0px{padding-left:0px}
|
||||
.rn-position\\:relative{position:relative}
|
||||
.rn-textAlign\\:inherit{text-align:inherit}
|
||||
.rn-textDecoration\\:none{text-decoration:none}
|
||||
.rn-flexShrink\\:0{-ms-flex-negative:0px;-webkit-flex-shrink:0px;flex-shrink:0}
|
||||
</style>"
|
||||
`;
|
||||
@@ -1,15 +1,15 @@
|
||||
/* eslint-env jasmine, jest */
|
||||
|
||||
import { prerenderApplication } from '../renderApplication';
|
||||
import { getApplication } from '../renderApplication';
|
||||
import React from 'react';
|
||||
|
||||
const component = () => <div />;
|
||||
|
||||
describe('apis/AppRegistry/renderApplication', () => {
|
||||
test('prerenderApplication', () => {
|
||||
const { html, styleElement } = prerenderApplication(component, {});
|
||||
test('getApplication', () => {
|
||||
const { element, stylesheet } = getApplication(component, {});
|
||||
|
||||
expect(html.indexOf('<div ') > -1).toBeTruthy();
|
||||
expect(styleElement.type).toEqual('style');
|
||||
expect(element).toBeTruthy();
|
||||
expect(stylesheet).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,9 +8,10 @@
|
||||
|
||||
import { Component } from 'react';
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
import ReactDOM from 'react-dom';
|
||||
import renderApplication, { prerenderApplication } from './renderApplication';
|
||||
import { unmountComponentAtNode } from 'react-dom';
|
||||
import renderApplication, { getApplication } from './renderApplication';
|
||||
|
||||
const emptyObject = {};
|
||||
const runnables = {};
|
||||
|
||||
type ComponentProvider = () => Component<any, any, any>
|
||||
@@ -29,20 +30,20 @@ class AppRegistry {
|
||||
return Object.keys(runnables);
|
||||
}
|
||||
|
||||
static prerenderApplication(appKey: string, appParameters?: Object): string {
|
||||
static getApplication(appKey: string, appParameters?: Object): string {
|
||||
invariant(
|
||||
runnables[appKey] && runnables[appKey].prerender,
|
||||
runnables[appKey] && runnables[appKey].getApplication,
|
||||
`Application ${appKey} has not been registered. ` +
|
||||
'This is either due to an import error during initialization or failure to call AppRegistry.registerComponent.'
|
||||
);
|
||||
|
||||
return runnables[appKey].prerender(appParameters);
|
||||
return runnables[appKey].getApplication(appParameters);
|
||||
}
|
||||
|
||||
static registerComponent(appKey: string, getComponentFunc: ComponentProvider): string {
|
||||
runnables[appKey] = {
|
||||
run: ({ initialProps, rootTag }) => renderApplication(getComponentFunc(), initialProps, rootTag),
|
||||
prerender: ({ initialProps } = {}) => prerenderApplication(getComponentFunc(), initialProps)
|
||||
getApplication: ({ initialProps } = emptyObject) => getApplication(getComponentFunc(), initialProps),
|
||||
run: ({ initialProps = emptyObject, rootTag }) => renderApplication(getComponentFunc(), initialProps, rootTag)
|
||||
};
|
||||
return appKey;
|
||||
}
|
||||
@@ -85,7 +86,7 @@ class AppRegistry {
|
||||
}
|
||||
|
||||
static unmountApplicationComponentAtRootTag(rootTag) {
|
||||
ReactDOM.unmountComponentAtNode(rootTag);
|
||||
unmountComponentAtNode(rootTag);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
*/
|
||||
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
import ReactDOM from 'react-dom';
|
||||
import ReactDOMServer from 'react-dom/server';
|
||||
import { render } from 'react-dom/lib/ReactMount';
|
||||
import ReactNativeApp from './ReactNativeApp';
|
||||
import StyleSheet from '../../apis/StyleSheet';
|
||||
import React, { Component } from 'react';
|
||||
@@ -23,17 +22,16 @@ export default function renderApplication(RootComponent: Component, initialProps
|
||||
rootTag={rootTag}
|
||||
/>
|
||||
);
|
||||
ReactDOM.render(component, rootTag);
|
||||
render(component, rootTag);
|
||||
}
|
||||
|
||||
export function prerenderApplication(RootComponent: Component, initialProps: Object): string {
|
||||
const component = (
|
||||
export function getApplication(RootComponent: Component, initialProps: Object): Object {
|
||||
const element = (
|
||||
<ReactNativeApp
|
||||
initialProps={initialProps}
|
||||
rootComponent={RootComponent}
|
||||
/>
|
||||
);
|
||||
const html = ReactDOMServer.renderToString(component);
|
||||
const styleElement = StyleSheet.render();
|
||||
return { html, styleElement };
|
||||
const stylesheet = StyleSheet.renderToString();
|
||||
return { element, stylesheet };
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
|
||||
import findIndex from 'lodash/findIndex';
|
||||
import findIndex from 'array-find-index';
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
|
||||
const EVENT_TYPES = [ 'change' ];
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*/
|
||||
import merge from 'lodash/merge';
|
||||
import merge from 'deep-assign';
|
||||
|
||||
const mergeLocalStorageItem = (key, value) => {
|
||||
const oldValue = window.localStorage.getItem(key);
|
||||
|
||||
28
src/apis/BackAndroid/index.js
Normal file
28
src/apis/BackAndroid/index.js
Normal file
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* web stub for BackAndroid.android.js
|
||||
*
|
||||
* @providesModule BackAndroid
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
function emptyFunction() {}
|
||||
|
||||
const BackAndroid = {
|
||||
exitApp: emptyFunction,
|
||||
addEventListener() {
|
||||
return {
|
||||
remove: emptyFunction
|
||||
};
|
||||
},
|
||||
removeEventListener: emptyFunction
|
||||
};
|
||||
|
||||
module.exports = BackAndroid;
|
||||
21
src/apis/Clipboard/index.js
Normal file
21
src/apis/Clipboard/index.js
Normal file
@@ -0,0 +1,21 @@
|
||||
class Clipboard {
|
||||
static getString() {
|
||||
return Promise.resolve('');
|
||||
}
|
||||
|
||||
static setString(text) {
|
||||
let success = false;
|
||||
const textField = document.createElement('textarea');
|
||||
textField.innerText = text;
|
||||
document.body.appendChild(textField);
|
||||
textField.select();
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
success = true;
|
||||
} catch (e) {}
|
||||
document.body.removeChild(textField);
|
||||
return success;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Clipboard;
|
||||
@@ -6,7 +6,7 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import debounce from 'lodash/debounce';
|
||||
import debounce from 'debounce';
|
||||
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
|
||||
|
||||
@@ -6,13 +6,12 @@
|
||||
*/
|
||||
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
import keyMirror from 'fbjs/lib/keyMirror';
|
||||
|
||||
const InteractionManager = {
|
||||
Events: keyMirror({
|
||||
interactionStart: true,
|
||||
interactionComplete: true
|
||||
}),
|
||||
Events: {
|
||||
interactionStart: 'interactionStart',
|
||||
interactionComplete: 'interactionComplete'
|
||||
},
|
||||
|
||||
/**
|
||||
* Schedule a function to run after all interactions have completed.
|
||||
|
||||
36
src/apis/Linking/index.js
Normal file
36
src/apis/Linking/index.js
Normal file
@@ -0,0 +1,36 @@
|
||||
const Linking = {
|
||||
addEventListener() {},
|
||||
removeEventListener() {},
|
||||
canOpenURL() { return true; },
|
||||
getInitialURL() { return ''; },
|
||||
openURL(url) {
|
||||
iframeOpen(url);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Tabs opened using JavaScript may redirect the parent tab using
|
||||
* `window.opener.location`, ignoring cross-origin restrictions and enabling
|
||||
* phishing attacks.
|
||||
*
|
||||
* Safari requires that we open the url by injecting a hidden iframe that calls
|
||||
* window.open(), then removes the iframe from the DOM.
|
||||
*
|
||||
* https://mathiasbynens.github.io/rel-noopener/
|
||||
*/
|
||||
const iframeOpen = (url) => {
|
||||
const iframe = document.createElement('iframe');
|
||||
iframe.style.display = 'none';
|
||||
document.body.appendChild(iframe);
|
||||
|
||||
const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
|
||||
const script = iframeDoc.createElement('script');
|
||||
script.text = `
|
||||
window.parent = null; window.top = null; window.frameElement = null;
|
||||
var child = window.open("${url}"); child.opener = null;
|
||||
`;
|
||||
iframeDoc.body.appendChild(script);
|
||||
document.body.removeChild(iframe);
|
||||
};
|
||||
|
||||
module.exports = Linking;
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
|
||||
import findIndex from 'lodash/findIndex';
|
||||
import findIndex from 'array-find-index';
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
|
||||
const connection = ExecutionEnvironment.canUseDOM && (
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
var TouchHistoryMath = require('react/lib/TouchHistoryMath');
|
||||
var TouchHistoryMath = require('react-dom/lib/TouchHistoryMath');
|
||||
|
||||
var currentCentroidXOfTouchesChangedAfter =
|
||||
TouchHistoryMath.currentCentroidXOfTouchesChangedAfter;
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
|
||||
import { PropTypes } from 'react'
|
||||
import ImageStylePropTypes from '../../components/Image/ImageStylePropTypes'
|
||||
import ReactPropTypeLocations from 'react/lib/ReactPropTypeLocations'
|
||||
import ReactPropTypesSecret from 'react/lib/ReactPropTypesSecret'
|
||||
import ReactPropTypeLocations from 'react-dom/lib/ReactPropTypeLocations'
|
||||
import ReactPropTypesSecret from 'react-dom/lib/ReactPropTypesSecret'
|
||||
import TextStylePropTypes from '../../components/Text/TextStylePropTypes'
|
||||
import ViewStylePropTypes from '../../components/View/ViewStylePropTypes'
|
||||
import warning from 'fbjs/lib/warning'
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
exports[`apis/StyleSheet/createReactDOMStyle converts ReactNative style to ReactDOM style 1`] = `
|
||||
Object {
|
||||
"borderBottomWidth": "1px",
|
||||
"borderLeftWidth": "1px",
|
||||
"borderRightWidth": "1px",
|
||||
"borderTopWidth": "1px",
|
||||
"borderWidthLeft": "2px",
|
||||
"borderWidthRight": "3px",
|
||||
"boxShadow": "1px 1px 1px 1px #000, 1px 2px 0px rgba(255,0,0,1)",
|
||||
"display": "flex",
|
||||
"marginBottom": "0px",
|
||||
"marginTop": "0px",
|
||||
"opacity": 0,
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,26 @@
|
||||
exports[`apis/StyleSheet/flattenStyle should merge style objects 1`] = `
|
||||
Object {
|
||||
"opacity": 1,
|
||||
"order": 2,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`apis/StyleSheet/flattenStyle should override style properties 1`] = `
|
||||
Object {
|
||||
"backgroundColor": "#023c69",
|
||||
"order": null,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`apis/StyleSheet/flattenStyle should overwrite properties with \`undefined\` 1`] = `
|
||||
Object {
|
||||
"backgroundColor": undefined,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`apis/StyleSheet/flattenStyle should recursively flatten arrays 1`] = `
|
||||
Object {
|
||||
"opacity": 1,
|
||||
"order": 3,
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1 @@
|
||||
exports[`apis/StyleSheet/generateCss generates correct css 1`] = `"-webkit-transition-duration:0.1s;border-width-left:2px;border-width-right:3px;box-shadow:1px 1px 1px 1px #000;position:absolute;transition-duration:0.1s"`;
|
||||
@@ -1,10 +1,14 @@
|
||||
exports[`apis/StyleSheet resolve 1`] = `
|
||||
Object {
|
||||
"className": "test __style_df __style_pebn",
|
||||
"style": Object {
|
||||
"display": null,
|
||||
"opacity": 1,
|
||||
"pointerEvents": null,
|
||||
},
|
||||
}
|
||||
exports[`apis/StyleSheet renderToString 1`] = `
|
||||
"<style id=\"react-native-stylesheet\">
|
||||
.rn-borderTopColor\\:red{border-top-color:red}
|
||||
.rn-borderRightColor\\:red{border-right-color:red}
|
||||
.rn-borderBottomColor\\:red{border-bottom-color:red}
|
||||
.rn-borderLeftColor\\:red{border-left-color:red}
|
||||
.rn-borderTopWidth\\:0px{border-top-width:0px}
|
||||
.rn-borderRightWidth\\:0px{border-right-width:0px}
|
||||
.rn-borderBottomWidth\\:0px{border-bottom-width:0px}
|
||||
.rn-borderLeftWidth\\:0px{border-left-width:0px}
|
||||
.rn-left\\:50px{left:50px}
|
||||
.rn-position\\:absolute{position:absolute}
|
||||
</style>"
|
||||
`;
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
exports[`apis/StyleSheet/registry resolve with register, resolves to className 1`] = `
|
||||
Object {
|
||||
"className": "
|
||||
rn-borderTopColor:red
|
||||
rn-borderRightColor:red
|
||||
rn-borderBottomColor:red
|
||||
rn-borderLeftColor:red
|
||||
rn-borderTopWidth:0px
|
||||
rn-borderRightWidth:0px
|
||||
rn-borderBottomWidth:0px
|
||||
rn-borderLeftWidth:0px
|
||||
rn-left:50px
|
||||
rn-pointerEvents:box-only
|
||||
rn-position:absolute
|
||||
rn-width:100px",
|
||||
"style": Object {},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`apis/StyleSheet/registry resolve with register, resolves to className 2`] = `
|
||||
Object {
|
||||
"className": "
|
||||
rn-borderTopColor:red
|
||||
rn-borderRightColor:red
|
||||
rn-borderBottomColor:red
|
||||
rn-borderLeftColor:red
|
||||
rn-borderTopWidth:0px
|
||||
rn-borderRightWidth:0px
|
||||
rn-borderBottomWidth:0px
|
||||
rn-borderLeftWidth:0px
|
||||
rn-left:50px
|
||||
rn-pointerEvents:box-only
|
||||
rn-position:absolute
|
||||
rn-width:200px",
|
||||
"style": Object {},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`apis/StyleSheet/registry resolve with register, resolves to className 3`] = `
|
||||
Object {
|
||||
"className": "
|
||||
rn-borderTopColor:red
|
||||
rn-borderRightColor:red
|
||||
rn-borderBottomColor:red
|
||||
rn-borderLeftColor:red
|
||||
rn-borderTopWidth:0px
|
||||
rn-borderRightWidth:0px
|
||||
rn-borderBottomWidth:0px
|
||||
rn-borderLeftWidth:0px
|
||||
rn-left:50px
|
||||
rn-pointerEvents:box-only
|
||||
rn-position:absolute
|
||||
rn-width:100px",
|
||||
"style": Object {},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`apis/StyleSheet/registry resolve with register, resolves to mixed 1`] = `
|
||||
Object {
|
||||
"className": "
|
||||
rn-left:50px
|
||||
rn-pointerEvents:box-only
|
||||
rn-position:absolute",
|
||||
"style": Object {
|
||||
"borderBottomColor": "red",
|
||||
"borderBottomWidth": "0px",
|
||||
"borderLeftColor": "red",
|
||||
"borderLeftWidth": "0px",
|
||||
"borderRightColor": "red",
|
||||
"borderRightWidth": "0px",
|
||||
"borderTopColor": "red",
|
||||
"borderTopWidth": "0px",
|
||||
"width": "100px",
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`apis/StyleSheet/registry resolve with register, resolves to mixed 2`] = `
|
||||
Object {
|
||||
"className": "
|
||||
rn-left:50px
|
||||
rn-pointerEvents:box-only
|
||||
rn-position:absolute
|
||||
rn-width:200px",
|
||||
"style": Object {
|
||||
"borderBottomColor": "red",
|
||||
"borderBottomWidth": "0px",
|
||||
"borderLeftColor": "red",
|
||||
"borderLeftWidth": "0px",
|
||||
"borderRightColor": "red",
|
||||
"borderRightWidth": "0px",
|
||||
"borderTopColor": "red",
|
||||
"borderTopWidth": "0px",
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`apis/StyleSheet/registry resolve with register, resolves to mixed 3`] = `
|
||||
Object {
|
||||
"className": "
|
||||
rn-left:50px
|
||||
rn-pointerEvents:box-only
|
||||
rn-position:absolute",
|
||||
"style": Object {
|
||||
"borderBottomColor": "red",
|
||||
"borderBottomWidth": "0px",
|
||||
"borderLeftColor": "red",
|
||||
"borderLeftWidth": "0px",
|
||||
"borderRightColor": "red",
|
||||
"borderRightWidth": "0px",
|
||||
"borderTopColor": "red",
|
||||
"borderTopWidth": "0px",
|
||||
"width": "100px",
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`apis/StyleSheet/registry resolve without register, resolves to inline styles 1`] = `
|
||||
Object {
|
||||
"className": "
|
||||
",
|
||||
"style": Object {
|
||||
"borderBottomColor": "red",
|
||||
"borderBottomWidth": "0px",
|
||||
"borderLeftColor": "red",
|
||||
"borderLeftWidth": "0px",
|
||||
"borderRightColor": "red",
|
||||
"borderRightWidth": "0px",
|
||||
"borderTopColor": "red",
|
||||
"borderTopWidth": "0px",
|
||||
"left": "50px",
|
||||
"pointerEvents": "box-only",
|
||||
"position": "absolute",
|
||||
"width": "100px",
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`apis/StyleSheet/registry resolve without register, resolves to inline styles 2`] = `
|
||||
Object {
|
||||
"className": "
|
||||
",
|
||||
"style": Object {
|
||||
"borderBottomColor": "red",
|
||||
"borderBottomWidth": "0px",
|
||||
"borderLeftColor": "red",
|
||||
"borderLeftWidth": "0px",
|
||||
"borderRightColor": "red",
|
||||
"borderRightWidth": "0px",
|
||||
"borderTopColor": "red",
|
||||
"borderTopWidth": "0px",
|
||||
"left": "50px",
|
||||
"pointerEvents": "box-only",
|
||||
"position": "absolute",
|
||||
"width": "200px",
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`apis/StyleSheet/registry resolve without register, resolves to inline styles 3`] = `
|
||||
Object {
|
||||
"className": "
|
||||
",
|
||||
"style": Object {
|
||||
"borderBottomColor": "red",
|
||||
"borderBottomWidth": "0px",
|
||||
"borderLeftColor": "red",
|
||||
"borderLeftWidth": "0px",
|
||||
"borderRightColor": "red",
|
||||
"borderRightWidth": "0px",
|
||||
"borderTopColor": "red",
|
||||
"borderTopWidth": "0px",
|
||||
"left": "50px",
|
||||
"pointerEvents": "box-only",
|
||||
"position": "absolute",
|
||||
"width": "100px",
|
||||
},
|
||||
}
|
||||
`;
|
||||
28
src/apis/StyleSheet/__tests__/createReactDOMStyle-test.js
Normal file
28
src/apis/StyleSheet/__tests__/createReactDOMStyle-test.js
Normal file
@@ -0,0 +1,28 @@
|
||||
/* eslint-env jasmine, jest */
|
||||
|
||||
import createReactDOMStyle from '../createReactDOMStyle';
|
||||
|
||||
const reactNativeStyle = {
|
||||
boxShadow: '1px 1px 1px 1px #000',
|
||||
borderWidthLeft: 2,
|
||||
borderWidth: 1,
|
||||
borderWidthRight: 3,
|
||||
display: 'flex',
|
||||
marginVertical: 0,
|
||||
opacity: 0,
|
||||
shadowColor: 'red',
|
||||
shadowOffset: { width: 1, height: 2 },
|
||||
resizeMode: 'contain'
|
||||
};
|
||||
|
||||
describe('apis/StyleSheet/createReactDOMStyle', () => {
|
||||
test('converts ReactNative style to ReactDOM style', () => {
|
||||
expect(createReactDOMStyle(reactNativeStyle)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('noop on DOM styles', () => {
|
||||
const firstStyle = createReactDOMStyle(reactNativeStyle);
|
||||
const secondStyle = createReactDOMStyle(firstStyle);
|
||||
expect(firstStyle).toEqual(secondStyle);
|
||||
});
|
||||
});
|
||||
@@ -1,12 +0,0 @@
|
||||
/* eslint-env jasmine, jest */
|
||||
|
||||
import createReactStyleObject from '../createReactStyleObject';
|
||||
|
||||
describe('apis/StyleSheet/createReactStyleObject', () => {
|
||||
test('converts ReactNative style to ReactDOM style', () => {
|
||||
const reactNativeStyle = { display: 'flex', marginVertical: 0, opacity: 0 };
|
||||
const expectedStyle = { display: 'flex', marginTop: '0px', marginBottom: '0px', opacity: 0 };
|
||||
|
||||
expect(createReactStyleObject(reactNativeStyle)).toEqual(expectedStyle);
|
||||
});
|
||||
});
|
||||
@@ -9,9 +9,9 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
import flattenStyle from '..';
|
||||
import flattenStyle from '../flattenStyle';
|
||||
|
||||
describe('modules/flattenStyle', () => {
|
||||
describe('apis/StyleSheet/flattenStyle', () => {
|
||||
test('should merge style objects', () => {
|
||||
const style = flattenStyle([
|
||||
{ opacity: 1 },
|
||||
16
src/apis/StyleSheet/__tests__/generateCss-test.js
Normal file
16
src/apis/StyleSheet/__tests__/generateCss-test.js
Normal file
@@ -0,0 +1,16 @@
|
||||
/* eslint-env jasmine, jest */
|
||||
|
||||
import generateCss from '../generateCss';
|
||||
|
||||
describe('apis/StyleSheet/generateCss', () => {
|
||||
test('generates correct css', () => {
|
||||
const style = {
|
||||
boxShadow: '1px 1px 1px 1px #000',
|
||||
borderWidthLeft: 2,
|
||||
borderWidthRight: 3,
|
||||
position: 'absolute',
|
||||
transitionDuration: '0.1s'
|
||||
};
|
||||
expect(generateCss(style)).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,12 +1,22 @@
|
||||
/* eslint-env jasmine, jest */
|
||||
|
||||
import { getDefaultStyleSheet } from '../css';
|
||||
import isPlainObject from 'lodash/isPlainObject';
|
||||
import StyleSheet from '..';
|
||||
import StyleRegistry from '../registry';
|
||||
|
||||
const isPlainObject = (x) => {
|
||||
const toString = Object.prototype.toString;
|
||||
let proto;
|
||||
/* eslint-disable */
|
||||
return (
|
||||
toString.call(x) === '[object Object]' &&
|
||||
(proto = Object.getPrototypeOf(x), proto === null || proto === Object.getPrototypeOf({}))
|
||||
);
|
||||
/* eslint-enable */
|
||||
};
|
||||
|
||||
describe('apis/StyleSheet', () => {
|
||||
beforeEach(() => {
|
||||
StyleSheet._reset();
|
||||
StyleRegistry.reset();
|
||||
});
|
||||
|
||||
test('absoluteFill', () => {
|
||||
@@ -22,11 +32,6 @@ describe('apis/StyleSheet', () => {
|
||||
const style = StyleSheet.create({ root: { opacity: 1 } });
|
||||
expect(Number.isInteger(style.root) === true).toBeTruthy();
|
||||
});
|
||||
|
||||
test('renders a style sheet in the browser', () => {
|
||||
StyleSheet.create({ root: { color: 'red' } });
|
||||
expect(document.getElementById('__react-native-style').textContent).toEqual(getDefaultStyleSheet());
|
||||
});
|
||||
});
|
||||
|
||||
test('flatten', () => {
|
||||
@@ -37,18 +42,17 @@ describe('apis/StyleSheet', () => {
|
||||
expect(Number.isInteger(StyleSheet.hairlineWidth) === true).toBeTruthy();
|
||||
});
|
||||
|
||||
test('render', () => {
|
||||
expect(StyleSheet.render().props.dangerouslySetInnerHTML.__html).toEqual(getDefaultStyleSheet());
|
||||
});
|
||||
|
||||
test('resolve', () => {
|
||||
expect(StyleSheet.resolve({
|
||||
className: 'test',
|
||||
style: {
|
||||
display: 'flex',
|
||||
opacity: 1,
|
||||
pointerEvents: 'box-none'
|
||||
test('renderToString', () => {
|
||||
StyleSheet.create({
|
||||
a: {
|
||||
borderWidth: 0,
|
||||
borderColor: 'red'
|
||||
},
|
||||
b: {
|
||||
position: 'absolute',
|
||||
left: 50
|
||||
}
|
||||
})).toMatchSnapshot();
|
||||
});
|
||||
expect(StyleSheet.renderToString()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
22
src/apis/StyleSheet/__tests__/injector.js
Normal file
22
src/apis/StyleSheet/__tests__/injector.js
Normal file
@@ -0,0 +1,22 @@
|
||||
/* eslint-env jasmine, jest */
|
||||
|
||||
import injector from '../injector';
|
||||
|
||||
describe('apis/StyleSheet/injector', () => {
|
||||
beforeEach(() => {
|
||||
document.head.insertAdjacentHTML('afterbegin', `
|
||||
<style id="react-native-stylesheet">
|
||||
.rn-alignItems\\:stretch{align-items:stretch;}
|
||||
.rn-position\\:top{position:top;}
|
||||
</style>
|
||||
`);
|
||||
});
|
||||
|
||||
test('hydrates from SSR', () => {
|
||||
const classList = injector.getClassNames();
|
||||
expect(classList).toEqual({
|
||||
'rn-alignItems\\:stretch': true,
|
||||
'rn-position\\:top': true
|
||||
});
|
||||
});
|
||||
});
|
||||
13
src/apis/StyleSheet/__tests__/prefixInlineStyles-test.js
Normal file
13
src/apis/StyleSheet/__tests__/prefixInlineStyles-test.js
Normal file
@@ -0,0 +1,13 @@
|
||||
/* eslint-env jasmine, jest */
|
||||
|
||||
import prefixInlineStyles from '../prefixInlineStyles';
|
||||
|
||||
describe('apis/StyleSheet/prefixInlineStyles', () => {
|
||||
test('handles array values', () => {
|
||||
const style = {
|
||||
display: [ '-webkit-flex', 'flex' ]
|
||||
};
|
||||
|
||||
expect(prefixInlineStyles(style)).toEqual({ display: 'flex' });
|
||||
});
|
||||
});
|
||||
@@ -1,20 +0,0 @@
|
||||
/* eslint-env jasmine, jest */
|
||||
|
||||
import processTextShadow from '../processTextShadow';
|
||||
|
||||
describe('apis/StyleSheet/processTextShadow', () => {
|
||||
test('textShadowOffset', () => {
|
||||
const style = {
|
||||
textShadowColor: 'red',
|
||||
textShadowOffset: { width: 2, height: 2 },
|
||||
textShadowRadius: 5
|
||||
};
|
||||
|
||||
expect(processTextShadow(style)).toEqual({
|
||||
textShadow: '2px 2px 5px red',
|
||||
textShadowColor: null,
|
||||
textShadowOffset: null,
|
||||
textShadowRadius: null
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,28 +0,0 @@
|
||||
/* eslint-env jasmine, jest */
|
||||
|
||||
import processTransform from '../processTransform';
|
||||
|
||||
describe('apis/StyleSheet/processTransform', () => {
|
||||
test('transform', () => {
|
||||
const style = {
|
||||
transform: [
|
||||
{ scaleX: 20 },
|
||||
{ translateX: 20 },
|
||||
{ rotate: '20deg' }
|
||||
]
|
||||
};
|
||||
|
||||
expect(processTransform(style)).toEqual({ transform: 'scaleX(20) translateX(20px) rotate(20deg)' });
|
||||
});
|
||||
|
||||
test('transformMatrix', () => {
|
||||
const style = {
|
||||
transformMatrix: [ 1, 2, 3, 4, 5, 6 ]
|
||||
};
|
||||
|
||||
expect(processTransform(style)).toEqual({
|
||||
transform: 'matrix3d(1,2,3,4,5,6)',
|
||||
transformMatrix: null
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,13 +0,0 @@
|
||||
/* eslint-env jasmine, jest */
|
||||
|
||||
import processVendorPrefixes from '../processVendorPrefixes';
|
||||
|
||||
describe('apis/StyleSheet/processVendorPrefixes', () => {
|
||||
test('handles array values', () => {
|
||||
const style = {
|
||||
display: [ '-webkit-flex', 'flex' ]
|
||||
};
|
||||
|
||||
expect(processVendorPrefixes(style)).toEqual({ display: 'flex' });
|
||||
});
|
||||
});
|
||||
54
src/apis/StyleSheet/__tests__/registry-test.js
Normal file
54
src/apis/StyleSheet/__tests__/registry-test.js
Normal file
@@ -0,0 +1,54 @@
|
||||
/* eslint-env jasmine, jest */
|
||||
|
||||
import StyleRegistry from '../registry';
|
||||
|
||||
describe('apis/StyleSheet/registry', () => {
|
||||
beforeEach(() => {
|
||||
StyleRegistry.reset();
|
||||
});
|
||||
|
||||
test('register', () => {
|
||||
const style = { opacity: 0 };
|
||||
const id = StyleRegistry.register(style);
|
||||
expect(typeof id === 'number').toBe(true);
|
||||
});
|
||||
|
||||
describe('resolve', () => {
|
||||
const styleA = { borderWidth: 0, borderColor: 'red', width: 100 };
|
||||
const styleB = { position: 'absolute', left: 50, pointerEvents: 'box-only' };
|
||||
const styleC = { width: 200 };
|
||||
|
||||
const testResolve = (a, b, c) => {
|
||||
// no common properties, different resolving order, same result
|
||||
const resolve1 = StyleRegistry.resolve([ a, b ]);
|
||||
expect(resolve1).toMatchSnapshot();
|
||||
const resolve2 = StyleRegistry.resolve([ b, a ]);
|
||||
expect(resolve1).toEqual(resolve2);
|
||||
|
||||
// common properties, different resolving order, different result
|
||||
const resolve3 = StyleRegistry.resolve([ a, b, c ]);
|
||||
expect(resolve3).toMatchSnapshot();
|
||||
const resolve4 = StyleRegistry.resolve([ c, a, b ]);
|
||||
expect(resolve4).toMatchSnapshot();
|
||||
expect(resolve3).not.toEqual(resolve4);
|
||||
};
|
||||
|
||||
test('with register, resolves to className', () => {
|
||||
const a = StyleRegistry.register(styleA);
|
||||
const b = StyleRegistry.register(styleB);
|
||||
const c = StyleRegistry.register(styleC);
|
||||
testResolve(a, b, c);
|
||||
});
|
||||
|
||||
test('with register, resolves to mixed', () => {
|
||||
const a = styleA;
|
||||
const b = StyleRegistry.register(styleB);
|
||||
const c = StyleRegistry.register(styleC);
|
||||
testResolve(a, b, c);
|
||||
});
|
||||
|
||||
test('without register, resolves to inline styles', () => {
|
||||
testResolve(styleA, styleB, styleC);
|
||||
});
|
||||
});
|
||||
});
|
||||
60
src/apis/StyleSheet/__tests__/resolveBoxShadow-test.js
Normal file
60
src/apis/StyleSheet/__tests__/resolveBoxShadow-test.js
Normal file
@@ -0,0 +1,60 @@
|
||||
/* eslint-env jasmine, jest */
|
||||
|
||||
import resolveBoxShadow from '../resolveBoxShadow';
|
||||
|
||||
describe('apis/StyleSheet/resolveBoxShadow', () => {
|
||||
test('shadowColor only', () => {
|
||||
const resolvedStyle = {};
|
||||
const style = { shadowColor: 'red' };
|
||||
resolveBoxShadow(resolvedStyle, style);
|
||||
|
||||
expect(resolvedStyle).toEqual({
|
||||
boxShadow: '0px 0px 0px rgba(255,0,0,1)'
|
||||
});
|
||||
});
|
||||
|
||||
test('shadowColor and shadowOpacity only', () => {
|
||||
const resolvedStyle = {};
|
||||
const style = { shadowColor: 'red', shadowOpacity: 0.5 };
|
||||
resolveBoxShadow(resolvedStyle, style);
|
||||
|
||||
expect(resolvedStyle).toEqual({
|
||||
boxShadow: '0px 0px 0px rgba(255,0,0,0.5)'
|
||||
});
|
||||
});
|
||||
|
||||
test('shadowOffset only', () => {
|
||||
const resolvedStyle = {};
|
||||
const style = { shadowOffset: { width: 1, height: 2 } };
|
||||
resolveBoxShadow(resolvedStyle, style);
|
||||
|
||||
expect(resolvedStyle).toEqual({
|
||||
boxShadow: '1px 2px 0px rgba(0,0,0,0)'
|
||||
});
|
||||
});
|
||||
|
||||
test('shadowRadius only', () => {
|
||||
const resolvedStyle = {};
|
||||
const style = { shadowRadius: 5 };
|
||||
resolveBoxShadow(resolvedStyle, style);
|
||||
|
||||
expect(resolvedStyle).toEqual({
|
||||
boxShadow: '0px 0px 5px rgba(0,0,0,0)'
|
||||
});
|
||||
});
|
||||
|
||||
test('shadowOffset, shadowRadius, shadowColor', () => {
|
||||
const resolvedStyle = {};
|
||||
const style = {
|
||||
shadowColor: 'rgba(50,60,70,0.5)',
|
||||
shadowOffset: { width: 1, height: 2 },
|
||||
shadowOpacity: 0.5,
|
||||
shadowRadius: 3
|
||||
};
|
||||
resolveBoxShadow(resolvedStyle, style);
|
||||
|
||||
expect(resolvedStyle).toEqual({
|
||||
boxShadow: '1px 2px 3px rgba(50,60,70,0.25)'
|
||||
});
|
||||
});
|
||||
});
|
||||
19
src/apis/StyleSheet/__tests__/resolveTextShadow-test.js
Normal file
19
src/apis/StyleSheet/__tests__/resolveTextShadow-test.js
Normal file
@@ -0,0 +1,19 @@
|
||||
/* eslint-env jasmine, jest */
|
||||
|
||||
import resolveTextShadow from '../resolveTextShadow';
|
||||
|
||||
describe('apis/StyleSheet/resolveTextShadow', () => {
|
||||
test('textShadowOffset', () => {
|
||||
const resolvedStyle = {};
|
||||
const style = {
|
||||
textShadowColor: 'red',
|
||||
textShadowOffset: { width: 1, height: 2 },
|
||||
textShadowRadius: 5
|
||||
};
|
||||
resolveTextShadow(resolvedStyle, style);
|
||||
|
||||
expect(resolvedStyle).toEqual({
|
||||
textShadow: '1px 2px 5px red'
|
||||
});
|
||||
});
|
||||
});
|
||||
32
src/apis/StyleSheet/__tests__/resolveTransform-test.js
Normal file
32
src/apis/StyleSheet/__tests__/resolveTransform-test.js
Normal file
@@ -0,0 +1,32 @@
|
||||
/* eslint-env jasmine, jest */
|
||||
|
||||
import resolveTransform from '../resolveTransform';
|
||||
|
||||
describe('apis/StyleSheet/resolveTransform', () => {
|
||||
test('transform', () => {
|
||||
const resolvedStyle = {};
|
||||
const style = {
|
||||
transform: [
|
||||
{ perspective: 50 },
|
||||
{ scaleX: 20 },
|
||||
{ translateX: 20 },
|
||||
{ rotate: '20deg' }
|
||||
]
|
||||
};
|
||||
resolveTransform(resolvedStyle, style);
|
||||
|
||||
expect(resolvedStyle).toEqual({
|
||||
transform: 'perspective(50px) scaleX(20) translateX(20px) rotate(20deg)'
|
||||
});
|
||||
});
|
||||
|
||||
test('transformMatrix', () => {
|
||||
const resolvedStyle = {};
|
||||
const style = { transformMatrix: [ 1, 2, 3, 4, 5, 6 ] };
|
||||
resolveTransform(resolvedStyle, style);
|
||||
|
||||
expect(resolvedStyle).toEqual({
|
||||
transform: 'matrix3d(1,2,3,4,5,6)'
|
||||
});
|
||||
});
|
||||
});
|
||||
6
src/apis/StyleSheet/createReactDOMStyle.js
Normal file
6
src/apis/StyleSheet/createReactDOMStyle.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import expandStyle from './expandStyle';
|
||||
import i18nStyle from './i18nStyle';
|
||||
|
||||
const createReactDOMStyle = (flattenedReactNativeStyle) => expandStyle(i18nStyle(flattenedReactNativeStyle));
|
||||
|
||||
module.exports = createReactDOMStyle;
|
||||
@@ -1,20 +0,0 @@
|
||||
import expandStyle from './expandStyle';
|
||||
import flattenStyle from '../../modules/flattenStyle';
|
||||
import i18nStyle from './i18nStyle';
|
||||
import processTextShadow from './processTextShadow';
|
||||
import processTransform from './processTransform';
|
||||
import processVendorPrefixes from './processVendorPrefixes';
|
||||
|
||||
const processors = [
|
||||
processTextShadow,
|
||||
processTransform,
|
||||
processVendorPrefixes
|
||||
];
|
||||
|
||||
const applyProcessors = (style) => processors.reduce((style, processor) => processor(style), style);
|
||||
|
||||
const createReactDOMStyleObject = (reactNativeStyle) => applyProcessors(
|
||||
expandStyle(i18nStyle(flattenStyle(reactNativeStyle)))
|
||||
);
|
||||
|
||||
module.exports = createReactDOMStyleObject;
|
||||
@@ -1,42 +0,0 @@
|
||||
const DISPLAY_FLEX_CLASSNAME = '__style_df';
|
||||
const POINTER_EVENTS_AUTO_CLASSNAME = '__style_pea';
|
||||
const POINTER_EVENTS_BOX_NONE_CLASSNAME = '__style_pebn';
|
||||
const POINTER_EVENTS_BOX_ONLY_CLASSNAME = '__style_pebo';
|
||||
const POINTER_EVENTS_NONE_CLASSNAME = '__style_pen';
|
||||
|
||||
/* eslint-disable max-len */
|
||||
const CSS_RESET =
|
||||
// reset unwanted styles
|
||||
'/* React Native */\n' +
|
||||
'html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}\n' +
|
||||
'body{margin:0}\n' +
|
||||
'button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}\n' +
|
||||
'input::-webkit-inner-spin-button,input::-webkit-outer-spin-button,' +
|
||||
'input::-webkit-search-cancel-button,input::-webkit-search-decoration,' +
|
||||
'input::-webkit-search-results-button,input::-webkit-search-results-decoration{display:none}';
|
||||
|
||||
const CSS_HELPERS =
|
||||
// vendor prefix 'display:flex' until React supports fallback values for inline styles
|
||||
`.${DISPLAY_FLEX_CLASSNAME} {display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex}\n` +
|
||||
// implement React Native's pointer event values
|
||||
`.${POINTER_EVENTS_AUTO_CLASSNAME}, .${POINTER_EVENTS_BOX_ONLY_CLASSNAME}, .${POINTER_EVENTS_BOX_NONE_CLASSNAME} * {pointer-events:auto}\n` +
|
||||
`.${POINTER_EVENTS_NONE_CLASSNAME}, .${POINTER_EVENTS_BOX_ONLY_CLASSNAME} *, .${POINTER_EVENTS_NONE_CLASSNAME} {pointer-events:none}`;
|
||||
/* eslint-enable max-len */
|
||||
|
||||
const styleAsClassName = {
|
||||
display: {
|
||||
'flex': DISPLAY_FLEX_CLASSNAME
|
||||
},
|
||||
pointerEvents: {
|
||||
'auto': POINTER_EVENTS_AUTO_CLASSNAME,
|
||||
'box-none': POINTER_EVENTS_BOX_NONE_CLASSNAME,
|
||||
'box-only': POINTER_EVENTS_BOX_ONLY_CLASSNAME,
|
||||
'none': POINTER_EVENTS_NONE_CLASSNAME
|
||||
}
|
||||
};
|
||||
|
||||
export const getDefaultStyleSheet = () => `${CSS_RESET}\n${CSS_HELPERS}`;
|
||||
|
||||
export const getStyleAsHelperClassName = (prop, value) => {
|
||||
return styleAsClassName[prop] && styleAsClassName[prop][value];
|
||||
};
|
||||
@@ -10,6 +10,9 @@
|
||||
*/
|
||||
|
||||
import normalizeValue from './normalizeValue';
|
||||
import resolveBoxShadow from './resolveBoxShadow';
|
||||
import resolveTextShadow from './resolveTextShadow';
|
||||
import resolveTransform from './resolveTransform';
|
||||
|
||||
const emptyObject = {};
|
||||
const styleShortFormProperties = {
|
||||
@@ -28,46 +31,83 @@ const styleShortFormProperties = {
|
||||
writingDirection: [ 'direction' ]
|
||||
};
|
||||
|
||||
const alphaSort = (arr) => arr.sort((a, b) => {
|
||||
const alphaSortProps = (propsArray) => propsArray.sort((a, b) => {
|
||||
if (a < b) { return -1; }
|
||||
if (a > b) { return 1; }
|
||||
return 0;
|
||||
});
|
||||
|
||||
const createStyleReducer = (originalStyle) => {
|
||||
const originalStyleProps = Object.keys(originalStyle);
|
||||
const expandStyle = (style) => {
|
||||
if (!style) { return emptyObject; }
|
||||
const styleProps = Object.keys(style);
|
||||
const sortedStyleProps = alphaSortProps(styleProps);
|
||||
let hasResolvedBoxShadow = false;
|
||||
let hasResolvedTextShadow = false;
|
||||
|
||||
return (style, prop) => {
|
||||
const value = normalizeValue(prop, originalStyle[prop]);
|
||||
const longFormProperties = styleShortFormProperties[prop];
|
||||
const reducer = (resolvedStyle, prop) => {
|
||||
const value = normalizeValue(prop, style[prop]);
|
||||
if (value == null) { return resolvedStyle; }
|
||||
|
||||
// React Native treats `flex:1` like `flex:1 1 auto`
|
||||
if (prop === 'flex') {
|
||||
style.flexGrow = value;
|
||||
style.flexShrink = 1;
|
||||
style.flexBasis = 'auto';
|
||||
// React Native accepts 'center' as a value
|
||||
} else if (prop === 'textAlignVertical') {
|
||||
style.verticalAlign = (value === 'center' ? 'middle' : value);
|
||||
} else if (longFormProperties) {
|
||||
longFormProperties.forEach((longForm, i) => {
|
||||
// the value of any longform property in the original styles takes
|
||||
// precedence over the shortform's value
|
||||
if (originalStyleProps.indexOf(longForm) === -1) {
|
||||
style[longForm] = value;
|
||||
switch (prop) {
|
||||
// ignore React Native styles
|
||||
case 'elevation':
|
||||
case 'resizeMode': {
|
||||
break;
|
||||
}
|
||||
case 'flex': {
|
||||
resolvedStyle.flexGrow = value;
|
||||
resolvedStyle.flexShrink = 1;
|
||||
resolvedStyle.flexBasis = 'auto';
|
||||
break;
|
||||
}
|
||||
case 'shadowColor':
|
||||
case 'shadowOffset':
|
||||
case 'shadowOpacity':
|
||||
case 'shadowRadius': {
|
||||
if (!hasResolvedBoxShadow) {
|
||||
resolveBoxShadow(resolvedStyle, style);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
style[prop] = value;
|
||||
hasResolvedBoxShadow = true;
|
||||
break;
|
||||
}
|
||||
case 'textAlignVertical': {
|
||||
resolvedStyle.verticalAlign = (value === 'center' ? 'middle' : value);
|
||||
break;
|
||||
}
|
||||
case 'textShadowColor':
|
||||
case 'textShadowOffset':
|
||||
case 'textShadowRadius': {
|
||||
if (!hasResolvedTextShadow) {
|
||||
resolveTextShadow(resolvedStyle, style);
|
||||
}
|
||||
hasResolvedTextShadow = true;
|
||||
break;
|
||||
}
|
||||
case 'transform': {
|
||||
resolveTransform(resolvedStyle, style);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
const longFormProperties = styleShortFormProperties[prop];
|
||||
if (longFormProperties) {
|
||||
longFormProperties.forEach((longForm, i) => {
|
||||
// the value of any longform property in the original styles takes
|
||||
// precedence over the shortform's value
|
||||
if (styleProps.indexOf(longForm) === -1) {
|
||||
resolvedStyle[longForm] = value;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
resolvedStyle[prop] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return style;
|
||||
};
|
||||
};
|
||||
|
||||
const expandStyle = (style = emptyObject) => {
|
||||
const sortedStyleProps = alphaSort(Object.keys(style));
|
||||
const styleReducer = createStyleReducer(style);
|
||||
return sortedStyleProps.reduce(styleReducer, {});
|
||||
return resolvedStyle;
|
||||
};
|
||||
|
||||
const resolvedStyle = sortedStyleProps.reduce(reducer, {});
|
||||
return resolvedStyle;
|
||||
};
|
||||
|
||||
module.exports = expandStyle;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
@@ -10,10 +9,8 @@
|
||||
* @providesModule flattenStyle
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var ReactNativePropRegistry = require('../ReactNativePropRegistry');
|
||||
var invariant = require('fbjs/lib/invariant');
|
||||
import ReactNativePropRegistry from '../../modules/ReactNativePropRegistry';
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
|
||||
function getStyle(style) {
|
||||
if (typeof style === 'number') {
|
||||
@@ -22,22 +19,26 @@ function getStyle(style) {
|
||||
return style;
|
||||
}
|
||||
|
||||
function flattenStyle(style: ?StyleObj): ?Object {
|
||||
function flattenStyle(style) {
|
||||
if (!style) {
|
||||
return undefined;
|
||||
}
|
||||
invariant(style !== true, 'style may be false but not true');
|
||||
|
||||
if (process.env.NODE !== 'production') {
|
||||
invariant(style !== true, 'style may be false but not true');
|
||||
}
|
||||
|
||||
if (!Array.isArray(style)) {
|
||||
return getStyle(style);
|
||||
}
|
||||
|
||||
var result = {};
|
||||
for (var i = 0, styleLength = style.length; i < styleLength; ++i) {
|
||||
var computedStyle = flattenStyle(style[i]);
|
||||
const result = {};
|
||||
for (let i = 0, styleLength = style.length; i < styleLength; ++i) {
|
||||
const computedStyle = flattenStyle(style[i]);
|
||||
if (computedStyle) {
|
||||
for (var key in computedStyle) {
|
||||
result[key] = computedStyle[key];
|
||||
for (const key in computedStyle) {
|
||||
const value = computedStyle[key];
|
||||
result[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
23
src/apis/StyleSheet/generateCss.js
Normal file
23
src/apis/StyleSheet/generateCss.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import hyphenate from './hyphenate';
|
||||
import mapKeyValue from '../../modules/mapKeyValue';
|
||||
import normalizeValue from './normalizeValue';
|
||||
import prefixAll from 'inline-style-prefixer/static';
|
||||
|
||||
const createDeclarationString = (prop, val) => {
|
||||
const name = hyphenate(prop);
|
||||
const value = normalizeValue(prop, val);
|
||||
if (Array.isArray(val)) {
|
||||
return val.map((v) => `${name}:${v}`).join(';');
|
||||
}
|
||||
return `${name}:${value}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates valid CSS rule body from a JS object
|
||||
*
|
||||
* generateCss({ width: 20, color: 'blue' });
|
||||
* // => 'color:blue;width:20px'
|
||||
*/
|
||||
const generateCss = (style) => mapKeyValue(prefixAll(style), createDeclarationString).sort().join(';');
|
||||
|
||||
module.exports = generateCss;
|
||||
3
src/apis/StyleSheet/hyphenate.js
Normal file
3
src/apis/StyleSheet/hyphenate.js
Normal file
@@ -0,0 +1,3 @@
|
||||
const RE_1 = /([A-Z])/g;
|
||||
const RE_2 = /^ms-/;
|
||||
module.exports = (s) => s.replace(RE_1, '-$1').toLowerCase().replace(RE_2, '-ms-');
|
||||
@@ -1,6 +1,8 @@
|
||||
import I18nManager from '../I18nManager';
|
||||
import multiplyStyleLengthValue from '../../modules/multiplyStyleLengthValue';
|
||||
|
||||
const emptyObject = {};
|
||||
|
||||
/**
|
||||
* Map of property names to their BiDi equivalent.
|
||||
*/
|
||||
@@ -64,38 +66,40 @@ const swapLtrRtl = (value:String): String => {
|
||||
return value === 'ltr' ? 'rtl' : value === 'rtl' ? 'ltr' : value;
|
||||
};
|
||||
|
||||
const i18nStyle = (style = {}) => {
|
||||
const i18nStyle = (style = emptyObject) => {
|
||||
const newStyle = {};
|
||||
for (const prop in style) {
|
||||
if (style.hasOwnProperty(prop)) {
|
||||
const indexOfNoFlip = prop.indexOf('$noI18n');
|
||||
if (!Object.prototype.hasOwnProperty.call(style, prop)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (I18nManager.isRTL) {
|
||||
if (PROPERTIES_TO_SWAP[prop]) {
|
||||
const newProp = flipProperty(prop);
|
||||
newStyle[newProp] = style[prop];
|
||||
} else if (PROPERTIES_SWAP_LEFT_RIGHT[prop]) {
|
||||
newStyle[prop] = swapLeftRight(style[prop]);
|
||||
} else if (PROPERTIES_SWAP_LTR_RTL[prop]) {
|
||||
newStyle[prop] = swapLtrRtl(style[prop]);
|
||||
} else if (prop === 'textShadowOffset') {
|
||||
newStyle[prop] = style[prop];
|
||||
newStyle[prop].width = additiveInverse(style[prop].width);
|
||||
} else if (prop === 'transform') {
|
||||
newStyle[prop] = style[prop].map(flipTransform);
|
||||
} else if (indexOfNoFlip > -1) {
|
||||
const newProp = prop.substring(0, indexOfNoFlip);
|
||||
newStyle[newProp] = style[prop];
|
||||
} else {
|
||||
newStyle[prop] = style[prop];
|
||||
}
|
||||
const indexOfNoFlip = prop.indexOf('$noI18n');
|
||||
|
||||
if (I18nManager.isRTL) {
|
||||
if (PROPERTIES_TO_SWAP[prop]) {
|
||||
const newProp = flipProperty(prop);
|
||||
newStyle[newProp] = style[prop];
|
||||
} else if (PROPERTIES_SWAP_LEFT_RIGHT[prop]) {
|
||||
newStyle[prop] = swapLeftRight(style[prop]);
|
||||
} else if (PROPERTIES_SWAP_LTR_RTL[prop]) {
|
||||
newStyle[prop] = swapLtrRtl(style[prop]);
|
||||
} else if (prop === 'textShadowOffset') {
|
||||
newStyle[prop] = style[prop];
|
||||
newStyle[prop].width = additiveInverse(style[prop].width);
|
||||
} else if (prop === 'transform') {
|
||||
newStyle[prop] = style[prop].map(flipTransform);
|
||||
} else if (indexOfNoFlip > -1) {
|
||||
const newProp = prop.substring(0, indexOfNoFlip);
|
||||
newStyle[newProp] = style[prop];
|
||||
} else {
|
||||
if (indexOfNoFlip > -1) {
|
||||
const newProp = prop.substring(0, indexOfNoFlip);
|
||||
newStyle[newProp] = style[prop];
|
||||
} else {
|
||||
newStyle[prop] = style[prop];
|
||||
}
|
||||
newStyle[prop] = style[prop];
|
||||
}
|
||||
} else {
|
||||
if (indexOfNoFlip > -1) {
|
||||
const newProp = prop.substring(0, indexOfNoFlip);
|
||||
newStyle[newProp] = style[prop];
|
||||
} else {
|
||||
newStyle[prop] = style[prop];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,88 +1,26 @@
|
||||
import * as css from './css';
|
||||
import createReactStyleObject from './createReactStyleObject';
|
||||
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
|
||||
import flattenStyle from '../../modules/flattenStyle';
|
||||
import React from 'react';
|
||||
import ReactNativePropRegistry from '../../modules/ReactNativePropRegistry';
|
||||
import StyleSheetValidation from './StyleSheetValidation';
|
||||
import flattenStyle from './flattenStyle';
|
||||
import initialize from './initialize';
|
||||
import injector from './injector';
|
||||
import StyleRegistry from './registry';
|
||||
|
||||
let styleElement;
|
||||
let shouldInsertStyleSheet = ExecutionEnvironment.canUseDOM;
|
||||
|
||||
const STYLE_SHEET_ID = '__react-native-style';
|
||||
initialize();
|
||||
|
||||
const absoluteFillObject = { position: 'absolute', left: 0, right: 0, top: 0, bottom: 0 };
|
||||
|
||||
const defaultStyleSheet = css.getDefaultStyleSheet();
|
||||
|
||||
const insertStyleSheet = () => {
|
||||
// check if the server rendered the style sheet
|
||||
styleElement = document.getElementById(STYLE_SHEET_ID);
|
||||
// if not, inject the style sheet
|
||||
if (!styleElement) {
|
||||
document.head.insertAdjacentHTML(
|
||||
'afterbegin',
|
||||
`<style id="${STYLE_SHEET_ID}">${defaultStyleSheet}</style>`
|
||||
);
|
||||
shouldInsertStyleSheet = false;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* For testing
|
||||
* @private
|
||||
*/
|
||||
_reset() {
|
||||
if (styleElement) {
|
||||
document.head.removeChild(styleElement);
|
||||
styleElement = null;
|
||||
shouldInsertStyleSheet = true;
|
||||
}
|
||||
},
|
||||
|
||||
absoluteFill: ReactNativePropRegistry.register(absoluteFillObject),
|
||||
|
||||
absoluteFill: StyleRegistry.register(absoluteFillObject),
|
||||
absoluteFillObject,
|
||||
|
||||
create(styles) {
|
||||
if (shouldInsertStyleSheet) {
|
||||
insertStyleSheet();
|
||||
}
|
||||
|
||||
const result = {};
|
||||
for (const key in styles) {
|
||||
StyleSheetValidation.validateStyle(key, styles);
|
||||
result[key] = ReactNativePropRegistry.register(styles[key]);
|
||||
}
|
||||
Object.keys(styles).forEach((key) => {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
require('./StyleSheetValidation').validateStyle(key, styles);
|
||||
}
|
||||
result[key] = StyleRegistry.register(styles[key]);
|
||||
});
|
||||
return result;
|
||||
},
|
||||
|
||||
hairlineWidth: 1,
|
||||
|
||||
flatten: flattenStyle,
|
||||
|
||||
/* @platform web */
|
||||
render() {
|
||||
return <style dangerouslySetInnerHTML={{ __html: defaultStyleSheet }} id={STYLE_SHEET_ID} />;
|
||||
},
|
||||
|
||||
/**
|
||||
* Accepts React props and converts style declarations to classNames when necessary
|
||||
* @platform web
|
||||
*/
|
||||
resolve(props) {
|
||||
let className = props.className || '';
|
||||
const style = createReactStyleObject(props.style);
|
||||
for (const prop in style) {
|
||||
const value = style[prop];
|
||||
const replacementClassName = css.getStyleAsHelperClassName(prop, value);
|
||||
if (replacementClassName) {
|
||||
className += ` ${replacementClassName}`;
|
||||
style[prop] = null;
|
||||
}
|
||||
}
|
||||
|
||||
return { className, style };
|
||||
}
|
||||
renderToString: injector.getStyleSheetHtml
|
||||
};
|
||||
|
||||
39
src/apis/StyleSheet/initialize.js
Normal file
39
src/apis/StyleSheet/initialize.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import injector from './injector';
|
||||
import StyleRegistry from './registry';
|
||||
|
||||
const initialize = () => {
|
||||
injector.addRule(
|
||||
'reset',
|
||||
'/* React Native StyleSheet*/\n' +
|
||||
'html{' +
|
||||
'font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;' +
|
||||
'-webkit-tap-highlight-color:rgba(0,0,0,0)' +
|
||||
'}\n' +
|
||||
'body{margin:0}\n' +
|
||||
'button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}\n' +
|
||||
'input::-webkit-inner-spin-button,input::-webkit-outer-spin-button,' +
|
||||
'input::-webkit-search-cancel-button,input::-webkit-search-decoration,' +
|
||||
'input::-webkit-search-results-button,input::-webkit-search-results-decoration{display:none}'
|
||||
);
|
||||
injector.addRule(
|
||||
'keyframes',
|
||||
'@keyframes rn-ActivityIndicator-animation{' +
|
||||
'0%{-webkit-transform: rotate(0deg); transform: rotate(0deg);}' +
|
||||
'100%{-webkit-transform: rotate(360deg); transform: rotate(360deg);}' +
|
||||
'}\n' +
|
||||
'@keyframes rn-ProgressBar-animation{' +
|
||||
'0%{-webkit-transform: translateX(-100%); transform: translateX(-100%);}' +
|
||||
'100%{-webkit-transform: translateX(400%); transform: translateX(400%);}' +
|
||||
'}'
|
||||
);
|
||||
injector.addRule(
|
||||
'pointer-events',
|
||||
'.rn-pointerEvents\\:auto,.rn-pointerEvents\\:box-only,.rn-pointerEvents\\:box-none *{pointer-events:auto}' +
|
||||
'.rn-pointerEvents\\:none,.rn-pointerEvents\\:box-only *,.rn-pointerEvents\\:box-none{pointer-events:none}'
|
||||
);
|
||||
|
||||
const classNames = injector.getClassNames();
|
||||
StyleRegistry.initialize(classNames);
|
||||
};
|
||||
|
||||
export default initialize;
|
||||
106
src/apis/StyleSheet/injector.js
Normal file
106
src/apis/StyleSheet/injector.js
Normal file
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* Based on
|
||||
* https://github.com/lelandrichardson/react-primitives/blob/master/src/StyleSheet/injector.js
|
||||
*/
|
||||
|
||||
import asap from 'asap';
|
||||
|
||||
const emptyObject = {};
|
||||
const hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||
|
||||
const CLASSNAME_REXEP = /\.rn-([^{;\s]+)/g;
|
||||
const STYLE_ELEMENT_ID = 'react-native-stylesheet';
|
||||
|
||||
let registry = {};
|
||||
let isDirty = false;
|
||||
|
||||
/**
|
||||
* Registers a rule and requests an update to the style sheet
|
||||
*/
|
||||
const addRule = (key, rule) => {
|
||||
if (!registry[key]) {
|
||||
registry[key] = rule;
|
||||
isDirty = true;
|
||||
if (global.document) {
|
||||
asap(frame);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a string of the registered rules
|
||||
*/
|
||||
const getStyleText = () => {
|
||||
/* eslint prefer-template:0 */
|
||||
let result = '\n';
|
||||
for (const key in registry) {
|
||||
if (hasOwnProperty.call(registry, key)) {
|
||||
result += registry[key] + '\n';
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns an HTML string for server rendering
|
||||
*/
|
||||
const getStyleSheetHtml = () => `<style id="${STYLE_ELEMENT_ID}">${getStyleText()}</style>`;
|
||||
|
||||
const reset = () => { registry = {}; };
|
||||
|
||||
/**
|
||||
* Finds or injects the style sheet when in a browser environment
|
||||
*/
|
||||
let styleNode = null;
|
||||
const getStyleNode = () => {
|
||||
if (global.document) {
|
||||
if (!styleNode) {
|
||||
// look for existing style sheet (could also be server-rendered)
|
||||
styleNode = document.getElementById(STYLE_ELEMENT_ID);
|
||||
if (!styleNode) {
|
||||
// if there is no existing stylesheet, inject it style sheet
|
||||
document.head.insertAdjacentHTML('afterbegin', getStyleSheetHtml());
|
||||
styleNode = document.getElementById(STYLE_ELEMENT_ID);
|
||||
}
|
||||
}
|
||||
return styleNode;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines which classes are available in the existing document. Doesn't
|
||||
* rely on the registry so it can be used to read class names from a SSR style
|
||||
* sheet.
|
||||
*/
|
||||
const getClassNames = () => {
|
||||
const styleNode = getStyleNode();
|
||||
if (styleNode) {
|
||||
const text = styleNode.textContent;
|
||||
const matches = text.match(CLASSNAME_REXEP);
|
||||
if (matches) {
|
||||
return matches.map((name) => name.slice(1)).reduce((classMap, className) => {
|
||||
classMap[className] = true;
|
||||
return classMap;
|
||||
}, {});
|
||||
}
|
||||
}
|
||||
return emptyObject;
|
||||
};
|
||||
|
||||
const frame = () => {
|
||||
if (!isDirty || !global.document) { return; }
|
||||
isDirty = false;
|
||||
|
||||
const styleNode = getStyleNode();
|
||||
if (styleNode) {
|
||||
const css = getStyleText();
|
||||
styleNode.textContent = css;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
addRule,
|
||||
getClassNames,
|
||||
getStyleSheetHtml,
|
||||
reset
|
||||
};
|
||||
@@ -1,30 +1,45 @@
|
||||
const unitlessNumbers = {
|
||||
animationIterationCount: true,
|
||||
borderImageOutset: true,
|
||||
borderImageSlice: true,
|
||||
borderImageWidth: true,
|
||||
boxFlex: true,
|
||||
boxFlexGroup: true,
|
||||
boxOrdinalGroup: true,
|
||||
columnCount: true,
|
||||
flex: true,
|
||||
flexGrow: true,
|
||||
flexOrder: true,
|
||||
flexPositive: true,
|
||||
flexShrink: true,
|
||||
flexNegative: true,
|
||||
fontWeight: true,
|
||||
gridRow: true,
|
||||
gridColumn: true,
|
||||
lineClamp: true,
|
||||
opacity: true,
|
||||
order: true,
|
||||
orphans: true,
|
||||
tabSize: true,
|
||||
widows: true,
|
||||
zIndex: true,
|
||||
zoom: true,
|
||||
// SVG-related
|
||||
fillOpacity: true,
|
||||
floodOpacity: true,
|
||||
stopOpacity: true,
|
||||
strokeDasharray: true,
|
||||
strokeDashoffset: true,
|
||||
strokeMiterlimit: true,
|
||||
strokeOpacity: true,
|
||||
strokeWidth: true,
|
||||
// transform types
|
||||
scale: true,
|
||||
scaleX: true,
|
||||
scaleY: true,
|
||||
scaleZ: true
|
||||
scaleZ: true,
|
||||
// RN properties
|
||||
shadowOpacity: true
|
||||
};
|
||||
|
||||
const normalizeValue = (property, value) => {
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import prefixAll from 'inline-style-prefixer/static';
|
||||
|
||||
const processVendorPrefixes = (style) => {
|
||||
const prefixInlineStyles = (style) => {
|
||||
const prefixedStyles = prefixAll(style);
|
||||
|
||||
// React@15 removed undocumented support for fallback values in
|
||||
// inline-styles. Revert array values to the standard CSS value
|
||||
for (const prop in prefixedStyles) {
|
||||
Object.keys(prefixedStyles).forEach((prop) => {
|
||||
const value = prefixedStyles[prop];
|
||||
if (Array.isArray(value)) {
|
||||
prefixedStyles[prop] = value[value.length - 1];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return prefixedStyles;
|
||||
};
|
||||
|
||||
module.exports = processVendorPrefixes;
|
||||
module.exports = prefixInlineStyles;
|
||||
@@ -1,19 +0,0 @@
|
||||
import normalizeValue from './normalizeValue';
|
||||
|
||||
const processTextShadow = (style) => {
|
||||
if (style && style.textShadowOffset) {
|
||||
const { height, width } = style.textShadowOffset;
|
||||
const offsetX = normalizeValue(null, height || 0);
|
||||
const offsetY = normalizeValue(null, width || 0);
|
||||
const blurRadius = normalizeValue(null, style.textShadowRadius || 0);
|
||||
const color = style.textShadowColor || 'currentcolor';
|
||||
|
||||
style.textShadow = `${offsetX} ${offsetY} ${blurRadius} ${color}`;
|
||||
style.textShadowColor = null;
|
||||
style.textShadowOffset = null;
|
||||
style.textShadowRadius = null;
|
||||
}
|
||||
return style;
|
||||
};
|
||||
|
||||
module.exports = processTextShadow;
|
||||
219
src/apis/StyleSheet/registry.js
Normal file
219
src/apis/StyleSheet/registry.js
Normal file
@@ -0,0 +1,219 @@
|
||||
/**
|
||||
* WARNING: changes to this file in particular can cause significant changes to
|
||||
* the results of render performance benchmarks.
|
||||
*/
|
||||
|
||||
import createReactDOMStyle from './createReactDOMStyle';
|
||||
import flattenArray from '../../modules/flattenArray';
|
||||
import flattenStyle from './flattenStyle';
|
||||
import generateCss from './generateCss';
|
||||
import injector from './injector';
|
||||
import mapKeyValue from '../../modules/mapKeyValue';
|
||||
import prefixInlineStyles from './prefixInlineStyles';
|
||||
import ReactNativePropRegistry from '../../modules/ReactNativePropRegistry';
|
||||
|
||||
const prefix = 'r-';
|
||||
const SPACE_REGEXP = /\s/g;
|
||||
const ESCAPE_SELECTOR_CHARS_REGEXP = /[(),":?.%\\$#*]/g;
|
||||
|
||||
/**
|
||||
* Creates an HTML class name for use on elements
|
||||
*/
|
||||
const createClassName = (prop, value) => {
|
||||
const val = `${value}`.replace(SPACE_REGEXP, '-');
|
||||
return `rn-${prop}:${val}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Formatting improves debugging in devtools and snapshot
|
||||
*/
|
||||
const mapDeclarationsToClassName = (style, fn) => {
|
||||
const result = mapKeyValue(style, fn).join('\n').trim();
|
||||
return `\n${result}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Inject a CSS rule for a given declaration and record the availability of the
|
||||
* resulting class name.
|
||||
*/
|
||||
let injectedClassNames = {};
|
||||
const injectClassNameIfNeeded = (prop, value) => {
|
||||
const className = createClassName(prop, value);
|
||||
if (!injectedClassNames[className]) {
|
||||
// create a valid CSS selector for a given HTML class name
|
||||
const selector = className.replace(ESCAPE_SELECTOR_CHARS_REGEXP, '\\$&');
|
||||
const body = generateCss({ [prop]: value });
|
||||
const css = `.${selector}{${body}}`;
|
||||
// this adds the rule to the buffer to be injected into the document
|
||||
injector.addRule(className, css);
|
||||
injectedClassNames[className] = true;
|
||||
}
|
||||
|
||||
return className;
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a React Native style object to HTML class names
|
||||
*/
|
||||
let resolvedPropsCache = {};
|
||||
const registerStyle = (id, flatStyle) => {
|
||||
const style = createReactDOMStyle(flatStyle);
|
||||
const className = mapDeclarationsToClassName(style, (prop, value) => {
|
||||
if (value != null) {
|
||||
return injectClassNameIfNeeded(prop, value);
|
||||
}
|
||||
});
|
||||
|
||||
const key = `${prefix}${id}`;
|
||||
resolvedPropsCache[key] = { className };
|
||||
|
||||
return id;
|
||||
};
|
||||
|
||||
/**
|
||||
* Resolves a React Native style object to DOM props
|
||||
*/
|
||||
const resolveProps = (reactNativeStyle) => {
|
||||
const flatStyle = flattenStyle(reactNativeStyle);
|
||||
const domStyle = createReactDOMStyle(flatStyle);
|
||||
const style = {};
|
||||
|
||||
const className = mapDeclarationsToClassName(domStyle, (prop, value) => {
|
||||
if (value != null) {
|
||||
const singleClassName = createClassName(prop, value);
|
||||
if (injectedClassNames[singleClassName]) {
|
||||
return singleClassName;
|
||||
} else {
|
||||
// 4x slower render
|
||||
style[prop] = value;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const props = {
|
||||
className,
|
||||
style: prefixInlineStyles(style)
|
||||
};
|
||||
|
||||
/*
|
||||
if (process.env.__REACT_NATIVE_DEBUG_ENABLED__) {
|
||||
console.groupCollapsed('[StyleSheet] resolving uncached styles');
|
||||
console.log(
|
||||
'Slow operation. Resolving style objects (uncached result). ' +
|
||||
'Occurs on first render and when using styles not registered with "StyleSheet.create"'
|
||||
);
|
||||
console.log('source => \n', reactNativeStyle);
|
||||
console.log('flatten => \n', flatStyle);
|
||||
console.log('resolve => \n', props);
|
||||
console.groupEnd();
|
||||
}
|
||||
*/
|
||||
|
||||
return props;
|
||||
};
|
||||
|
||||
/**
|
||||
* Caching layer over 'resolveProps'
|
||||
*/
|
||||
const resolvePropsIfNeeded = (key, style) => {
|
||||
if (key) {
|
||||
if (!resolvedPropsCache[key]) {
|
||||
// slow: convert style object to props and cache
|
||||
resolvedPropsCache[key] = resolveProps(style);
|
||||
}
|
||||
return resolvedPropsCache[key];
|
||||
}
|
||||
return resolveProps(style);
|
||||
};
|
||||
|
||||
/**
|
||||
* Web style registry
|
||||
*/
|
||||
const StyleRegistry = {
|
||||
initialize(classNames) {
|
||||
injectedClassNames = classNames;
|
||||
|
||||
/*
|
||||
if (process.env.__REACT_NATIVE_DEBUG_ENABLED__) {
|
||||
if (global.__REACT_NATIVE_DEBUG_ENABLED__styleRegistryTimer) {
|
||||
clearInterval(global.__REACT_NATIVE_DEBUG_ENABLED__styleRegistryTimer);
|
||||
}
|
||||
global.__REACT_NATIVE_DEBUG_ENABLED__styleRegistryTimer = setInterval(() => {
|
||||
const entryCount = Object.keys(resolvedPropsCache).length;
|
||||
console.groupCollapsed('[StyleSheet] resolved props cache snapshot:', entryCount, 'entries');
|
||||
console.log(resolvedPropsCache);
|
||||
console.groupEnd();
|
||||
}, 30000);
|
||||
}
|
||||
*/
|
||||
},
|
||||
|
||||
reset() {
|
||||
injectedClassNames = {};
|
||||
resolvedPropsCache = {};
|
||||
injector.reset();
|
||||
},
|
||||
|
||||
register(style) {
|
||||
const id = ReactNativePropRegistry.register(style);
|
||||
return registerStyle(id, style);
|
||||
},
|
||||
|
||||
resolve(reactNativeStyle) {
|
||||
if (!reactNativeStyle) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// fast and cachable
|
||||
if (typeof reactNativeStyle === 'number') {
|
||||
const key = `${prefix}${reactNativeStyle}`;
|
||||
return resolvePropsIfNeeded(key, reactNativeStyle);
|
||||
}
|
||||
|
||||
// convert a RN style object to DOM props
|
||||
if (!Array.isArray(reactNativeStyle)) {
|
||||
return resolveProps(reactNativeStyle);
|
||||
}
|
||||
|
||||
// flatten the array
|
||||
// [ 1, [ 2, 3 ], { prop: value }, 4, 5 ] => [ 1, 2, 3, { prop: value }, 4, 5 ];
|
||||
const flatArray = flattenArray(reactNativeStyle);
|
||||
|
||||
let isArrayOfNumbers = true;
|
||||
for (let i = 0; i < flatArray.length; i++) {
|
||||
if (typeof flatArray[i] !== 'number') {
|
||||
isArrayOfNumbers = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: determine when/if to cache unregistered styles. This produces 2x
|
||||
// faster benchmark results for unregistered styles. However, the cache
|
||||
// could be filled with props that are never used again.
|
||||
//
|
||||
// let hasValidKey = true;
|
||||
// let key = flatArray.reduce((keyParts, element) => {
|
||||
// if (typeof element === 'number') {
|
||||
// keyParts.push(element);
|
||||
// } else {
|
||||
// if (element.transform) {
|
||||
// hasValidKey = false;
|
||||
// } else {
|
||||
// const objectAsKey = Object.keys(element).map((prop) => `${prop}:${element[prop]}`).join(';');
|
||||
// if (objectAsKey !== '') {
|
||||
// keyParts.push(objectAsKey);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return keyParts;
|
||||
// }, [ prefix ]).join('-');
|
||||
// if (!hasValidKey) { key = null; }
|
||||
|
||||
// cache resolved props when all styles are registered
|
||||
const key = isArrayOfNumbers ? `${prefix}${flatArray.join('-')}` : null;
|
||||
|
||||
return resolvePropsIfNeeded(key, flatArray);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = StyleRegistry;
|
||||
26
src/apis/StyleSheet/resolveBoxShadow.js
Normal file
26
src/apis/StyleSheet/resolveBoxShadow.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import normalizeColor from 'normalize-css-color';
|
||||
import normalizeValue from './normalizeValue';
|
||||
|
||||
const defaultOffset = { height: 0, width: 0 };
|
||||
|
||||
const applyOpacity = (color, opacity = 1) => {
|
||||
const nullableColor = normalizeColor(color);
|
||||
const colorInt = nullableColor === null ? 0x00000000 : nullableColor;
|
||||
const { r, g, b, a } = normalizeColor.rgba(colorInt);
|
||||
const alpha = a.toFixed(2);
|
||||
return `rgba(${r},${g},${b},${alpha * opacity})`;
|
||||
};
|
||||
|
||||
// TODO: add inset and spread support
|
||||
const resolveBoxShadow = (resolvedStyle, style) => {
|
||||
const { height, width } = style.shadowOffset || defaultOffset;
|
||||
const offsetX = normalizeValue(null, width);
|
||||
const offsetY = normalizeValue(null, height);
|
||||
const blurRadius = normalizeValue(null, style.shadowRadius || 0);
|
||||
const color = applyOpacity(style.shadowColor, style.shadowOpacity);
|
||||
|
||||
const boxShadow = `${offsetX} ${offsetY} ${blurRadius} ${color}`;
|
||||
resolvedStyle.boxShadow = style.boxShadow ? `${style.boxShadow}, ${boxShadow}` : boxShadow;
|
||||
};
|
||||
|
||||
module.exports = resolveBoxShadow;
|
||||
15
src/apis/StyleSheet/resolveTextShadow.js
Normal file
15
src/apis/StyleSheet/resolveTextShadow.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import normalizeValue from './normalizeValue';
|
||||
|
||||
const defaultOffset = { height: 0, width: 0 };
|
||||
|
||||
const resolveTextShadow = (resolvedStyle, style) => {
|
||||
const { height, width } = style.textShadowOffset || defaultOffset;
|
||||
const offsetX = normalizeValue(null, width);
|
||||
const offsetY = normalizeValue(null, height);
|
||||
const blurRadius = normalizeValue(null, style.textShadowRadius || 0);
|
||||
const color = style.textShadowColor || 'currentcolor';
|
||||
|
||||
resolvedStyle.textShadow = `${offsetX} ${offsetY} ${blurRadius} ${color}`;
|
||||
};
|
||||
|
||||
module.exports = resolveTextShadow;
|
||||
@@ -1,7 +1,7 @@
|
||||
import normalizeValue from './normalizeValue';
|
||||
|
||||
// { scale: 2 } => 'scale(2)'
|
||||
// { translateX: 20 } => 'translateX(20px)'
|
||||
// { translateX: 20 } => 'translateX(20px)'
|
||||
const mapTransform = (transform) => {
|
||||
const type = Object.keys(transform)[0];
|
||||
const value = normalizeValue(type, transform[type]);
|
||||
@@ -14,16 +14,14 @@ const convertTransformMatrix = (transformMatrix) => {
|
||||
return `matrix3d(${matrix})`;
|
||||
};
|
||||
|
||||
const processTransform = (style) => {
|
||||
if (style) {
|
||||
if (style.transform && Array.isArray(style.transform)) {
|
||||
style.transform = style.transform.map(mapTransform).join(' ');
|
||||
} else if (style.transformMatrix) {
|
||||
style.transform = convertTransformMatrix(style.transformMatrix);
|
||||
style.transformMatrix = null;
|
||||
}
|
||||
const resolveTransform = (resolvedStyle, style) => {
|
||||
if (Array.isArray(style.transform)) {
|
||||
const transform = style.transform.map(mapTransform).join(' ');
|
||||
resolvedStyle.transform = transform;
|
||||
} else if (style.transformMatrix) {
|
||||
const transform = convertTransformMatrix(style.transformMatrix);
|
||||
resolvedStyle.transform = transform;
|
||||
}
|
||||
return style;
|
||||
};
|
||||
|
||||
module.exports = processTransform;
|
||||
module.exports = resolveTransform;
|
||||
@@ -10,107 +10,7 @@ const createNode = (style = {}) => {
|
||||
return root;
|
||||
};
|
||||
|
||||
let defaultBodyMargin;
|
||||
|
||||
describe('apis/UIManager', () => {
|
||||
beforeEach(() => {
|
||||
// remove default body margin so we can predict the measured offsets
|
||||
defaultBodyMargin = document.body.style.margin;
|
||||
document.body.style.margin = 0;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
document.body.style.margin = defaultBodyMargin;
|
||||
});
|
||||
|
||||
describe('measure', () => {
|
||||
test('provides correct layout to callback', () => {
|
||||
const node = createNode({ height: '5000px', left: '100px', position: 'relative', top: '100px', width: '5000px' });
|
||||
document.body.appendChild(node);
|
||||
|
||||
node.getBoundingClientRect = jest.fn(() => ({ width: 5000, height: 5000, top: 100, left: 100 }));
|
||||
|
||||
UIManager.measure(node, (x, y, width, height, pageX, pageY) => {
|
||||
expect(x).toEqual(100);
|
||||
expect(y).toEqual(100);
|
||||
expect(width).toEqual(5000);
|
||||
expect(height).toEqual(5000);
|
||||
expect(pageX).toEqual(100);
|
||||
expect(pageY).toEqual(100);
|
||||
});
|
||||
|
||||
// test values account for scroll position
|
||||
window.scrollTo(200, 200);
|
||||
node.getBoundingClientRect = jest.fn(() => ({ width: 5000, height: 5000, top: -100, left: -100 }));
|
||||
node.parentNode.getBoundingClientRect = jest.fn(() => ({ top: -200, left: -200 }));
|
||||
|
||||
UIManager.measure(node, (x, y, width, height, pageX, pageY) => {
|
||||
expect(x).toEqual(100);
|
||||
expect(y).toEqual(100);
|
||||
expect(width).toEqual(5000);
|
||||
expect(height).toEqual(5000);
|
||||
expect(pageX).toEqual(-100);
|
||||
expect(pageY).toEqual(-100);
|
||||
});
|
||||
|
||||
document.body.removeChild(node);
|
||||
});
|
||||
});
|
||||
|
||||
describe('measureLayout', () => {
|
||||
test('provides correct layout to onSuccess callback', () => {
|
||||
const node = createNode({ height: '10px', width: '10px' });
|
||||
const middle = createNode({ padding: '20px' });
|
||||
const context = createNode({ padding: '20px' });
|
||||
middle.appendChild(node);
|
||||
context.appendChild(middle);
|
||||
document.body.appendChild(context);
|
||||
|
||||
node.getBoundingClientRect = jest.fn(() => ({
|
||||
width: 10,
|
||||
height: 10,
|
||||
top: 40,
|
||||
left: 40
|
||||
}));
|
||||
|
||||
UIManager.measureLayout(node, context, () => {}, (x, y, width, height) => {
|
||||
expect(x).toEqual(40);
|
||||
expect(y).toEqual(40);
|
||||
expect(width).toEqual(10);
|
||||
expect(height).toEqual(10);
|
||||
});
|
||||
|
||||
document.body.removeChild(context);
|
||||
});
|
||||
});
|
||||
|
||||
describe('measureInWindow', () => {
|
||||
test('provides correct layout to callback', () => {
|
||||
const node = createNode({ height: '10px', width: '10px' });
|
||||
const middle = createNode({ padding: '20px' });
|
||||
const context = createNode({ padding: '20px' });
|
||||
middle.appendChild(node);
|
||||
context.appendChild(middle);
|
||||
document.body.appendChild(context);
|
||||
|
||||
node.getBoundingClientRect = jest.fn(() => ({
|
||||
width: 10,
|
||||
height: 10,
|
||||
top: 40,
|
||||
left: 40
|
||||
}));
|
||||
|
||||
UIManager.measureInWindow(node, (x, y, width, height) => {
|
||||
expect(x).toEqual(40);
|
||||
expect(y).toEqual(40);
|
||||
expect(width).toEqual(10);
|
||||
expect(height).toEqual(10);
|
||||
});
|
||||
|
||||
document.body.removeChild(context);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateView', () => {
|
||||
const componentStub = {
|
||||
_reactInternalInstance: {
|
||||
@@ -119,17 +19,16 @@ describe('apis/UIManager', () => {
|
||||
}
|
||||
};
|
||||
|
||||
test('add new className to existing className', () => {
|
||||
test('supports className alias for class', () => {
|
||||
const node = createNode();
|
||||
node.className = 'existing';
|
||||
const props = { className: 'extra' };
|
||||
UIManager.updateView(node, props, componentStub);
|
||||
expect(node.getAttribute('class')).toEqual('existing extra');
|
||||
expect(node.getAttribute('class')).toEqual('extra');
|
||||
});
|
||||
|
||||
test('adds correct DOM styles to existing style', () => {
|
||||
const node = createNode({ color: 'red' });
|
||||
const props = { style: { marginVertical: 0, opacity: 0 } };
|
||||
const props = { style: { marginTop: 0, marginBottom: 0, opacity: 0 } };
|
||||
UIManager.updateView(node, props, componentStub);
|
||||
expect(node.getAttribute('style')).toEqual('color: red; margin-top: 0px; margin-bottom: 0px; opacity: 0;');
|
||||
});
|
||||
|
||||
@@ -1,13 +1,28 @@
|
||||
import createReactStyleObject from '../StyleSheet/createReactStyleObject';
|
||||
import CSSPropertyOperations from 'react/lib/CSSPropertyOperations';
|
||||
import asap from 'asap';
|
||||
import CSSPropertyOperations from 'react-dom/lib/CSSPropertyOperations';
|
||||
|
||||
const _measureLayout = (node, relativeToNativeNode, callback) => {
|
||||
const relativeNode = relativeToNativeNode || node.parentNode;
|
||||
const relativeRect = relativeNode.getBoundingClientRect();
|
||||
const { height, left, top, width } = node.getBoundingClientRect();
|
||||
const x = left - relativeRect.left;
|
||||
const y = top - relativeRect.top;
|
||||
callback(x, y, width, height, left, top);
|
||||
const getRect = (node) => {
|
||||
const height = node.offsetHeight;
|
||||
const width = node.offsetWidth;
|
||||
let left = 0;
|
||||
let top = 0;
|
||||
while (node && node.nodeType === 1 /* Node.ELEMENT_NODE */) {
|
||||
left += node.offsetLeft;
|
||||
top += node.offsetTop;
|
||||
node = node.offsetParent;
|
||||
}
|
||||
return { height, left, top, width };
|
||||
};
|
||||
|
||||
const measureLayout = (node, relativeToNativeNode, callback) => {
|
||||
asap(() => {
|
||||
const relativeNode = relativeToNativeNode || node.parentNode;
|
||||
const relativeRect = getRect(relativeNode);
|
||||
const { height, left, top, width } = getRect(node);
|
||||
const x = left - relativeRect.left;
|
||||
const y = top - relativeRect.top;
|
||||
callback(x, y, width, height, left, top);
|
||||
});
|
||||
};
|
||||
|
||||
const UIManager = {
|
||||
@@ -20,38 +35,34 @@ const UIManager = {
|
||||
},
|
||||
|
||||
measure(node, callback) {
|
||||
_measureLayout(node, null, callback);
|
||||
measureLayout(node, null, callback);
|
||||
},
|
||||
|
||||
measureInWindow(node, callback) {
|
||||
const { height, left, top, width } = node.getBoundingClientRect();
|
||||
const { height, left, top, width } = getRect(node);
|
||||
callback(left, top, width, height);
|
||||
},
|
||||
|
||||
measureLayout(node, relativeToNativeNode, onFail, onSuccess) {
|
||||
const relativeTo = relativeToNativeNode || node.parentNode;
|
||||
_measureLayout(node, relativeTo, onSuccess);
|
||||
measureLayout(node, relativeTo, onSuccess);
|
||||
},
|
||||
|
||||
updateView(node, props, component /* only needed to surpress React errors in development */) {
|
||||
for (const prop in props) {
|
||||
const value = props[prop];
|
||||
if (!Object.prototype.hasOwnProperty.call(props, prop)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const value = props[prop];
|
||||
switch (prop) {
|
||||
case 'style':
|
||||
// convert styles to DOM-styles
|
||||
CSSPropertyOperations.setValueForStyles(
|
||||
node,
|
||||
createReactStyleObject(value),
|
||||
component._reactInternalInstance
|
||||
);
|
||||
case 'style': {
|
||||
CSSPropertyOperations.setValueForStyles(node, value, component._reactInternalInstance);
|
||||
break;
|
||||
}
|
||||
case 'class':
|
||||
case 'className': {
|
||||
const nativeProp = 'class';
|
||||
// prevent class names managed by React Native from being replaced
|
||||
const className = `${node.getAttribute(nativeProp)} ${value}`;
|
||||
node.setAttribute(nativeProp, className);
|
||||
node.setAttribute('class', value);
|
||||
break;
|
||||
}
|
||||
case 'text':
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
exports[`components/ActivityIndicator default render 1`] = `
|
||||
<div
|
||||
aria-valuemax="1"
|
||||
aria-valuemin="0"
|
||||
className="
|
||||
rn-alignItems:center
|
||||
rn-backgroundColor:transparent
|
||||
rn-borderTopStyle:solid
|
||||
rn-borderRightStyle:solid
|
||||
rn-borderBottomStyle:solid
|
||||
rn-borderLeftStyle:solid
|
||||
rn-borderTopWidth:0px
|
||||
rn-borderRightWidth:0px
|
||||
rn-borderBottomWidth:0px
|
||||
rn-borderLeftWidth:0px
|
||||
rn-boxSizing:border-box
|
||||
rn-color:inherit
|
||||
rn-display:flex
|
||||
rn-flexBasis:auto
|
||||
rn-flexDirection:column
|
||||
rn-flexShrink:0
|
||||
rn-font:inherit
|
||||
rn-justifyContent:center
|
||||
rn-listStyle:none
|
||||
rn-marginTop:0px
|
||||
rn-marginRight:0px
|
||||
rn-marginBottom:0px
|
||||
rn-marginLeft:0px
|
||||
rn-minHeight:0px
|
||||
rn-minWidth:0px
|
||||
rn-paddingTop:0px
|
||||
rn-paddingRight:0px
|
||||
rn-paddingBottom:0px
|
||||
rn-paddingLeft:0px
|
||||
rn-position:relative
|
||||
rn-textAlign:inherit
|
||||
rn-textDecoration:none"
|
||||
role="progressbar"
|
||||
style={Object {}}>
|
||||
<div
|
||||
className="
|
||||
rn-alignItems:stretch
|
||||
rn-animationDuration:0.75s
|
||||
rn-animationIterationCount:infinite
|
||||
rn-animationName:rn-ActivityIndicator-animation
|
||||
rn-animationTimingFunction:linear
|
||||
rn-backgroundColor:transparent
|
||||
rn-borderTopStyle:solid
|
||||
rn-borderRightStyle:solid
|
||||
rn-borderBottomStyle:solid
|
||||
rn-borderLeftStyle:solid
|
||||
rn-borderTopWidth:0px
|
||||
rn-borderRightWidth:0px
|
||||
rn-borderBottomWidth:0px
|
||||
rn-borderLeftWidth:0px
|
||||
rn-boxSizing:border-box
|
||||
rn-color:inherit
|
||||
rn-display:flex
|
||||
rn-flexBasis:auto
|
||||
rn-flexDirection:column
|
||||
rn-flexShrink:0
|
||||
rn-font:inherit
|
||||
rn-height:20px
|
||||
rn-listStyle:none
|
||||
rn-marginTop:0px
|
||||
rn-marginRight:0px
|
||||
rn-marginBottom:0px
|
||||
rn-marginLeft:0px
|
||||
rn-minHeight:0px
|
||||
rn-minWidth:0px
|
||||
rn-paddingTop:0px
|
||||
rn-paddingRight:0px
|
||||
rn-paddingBottom:0px
|
||||
rn-paddingLeft:0px
|
||||
rn-position:relative
|
||||
rn-textAlign:inherit
|
||||
rn-textDecoration:none
|
||||
rn-width:20px"
|
||||
style={Object {}}>
|
||||
<svg
|
||||
height="100%"
|
||||
viewBox="0 0 32 32"
|
||||
width="100%">
|
||||
<circle
|
||||
cx="16"
|
||||
cy="16"
|
||||
fill="none"
|
||||
r="14"
|
||||
strokeWidth="4"
|
||||
style={
|
||||
Object {
|
||||
"opacity": 0.2,
|
||||
"stroke": "#1976D2",
|
||||
}
|
||||
} />
|
||||
<circle
|
||||
cx="16"
|
||||
cy="16"
|
||||
fill="none"
|
||||
r="14"
|
||||
strokeWidth="4"
|
||||
style={
|
||||
Object {
|
||||
"stroke": "#1976D2",
|
||||
"strokeDasharray": 80,
|
||||
"strokeDashoffset": 60,
|
||||
}
|
||||
} />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`components/ActivityIndicator other render 1`] = `
|
||||
<div
|
||||
aria-valuemax="1"
|
||||
aria-valuemin="0"
|
||||
className="
|
||||
rn-alignItems:center
|
||||
rn-backgroundColor:transparent
|
||||
rn-borderTopStyle:solid
|
||||
rn-borderRightStyle:solid
|
||||
rn-borderBottomStyle:solid
|
||||
rn-borderLeftStyle:solid
|
||||
rn-borderTopWidth:0px
|
||||
rn-borderRightWidth:0px
|
||||
rn-borderBottomWidth:0px
|
||||
rn-borderLeftWidth:0px
|
||||
rn-boxSizing:border-box
|
||||
rn-color:inherit
|
||||
rn-display:flex
|
||||
rn-flexBasis:auto
|
||||
rn-flexDirection:column
|
||||
rn-flexShrink:0
|
||||
rn-font:inherit
|
||||
rn-justifyContent:center
|
||||
rn-listStyle:none
|
||||
rn-marginTop:0px
|
||||
rn-marginRight:0px
|
||||
rn-marginBottom:0px
|
||||
rn-marginLeft:0px
|
||||
rn-minHeight:0px
|
||||
rn-minWidth:0px
|
||||
rn-paddingTop:0px
|
||||
rn-paddingRight:0px
|
||||
rn-paddingBottom:0px
|
||||
rn-paddingLeft:0px
|
||||
rn-position:relative
|
||||
rn-textAlign:inherit
|
||||
rn-textDecoration:none"
|
||||
role="progressbar"
|
||||
style={Object {}}>
|
||||
<div
|
||||
className="
|
||||
rn-alignItems:stretch
|
||||
rn-animationDuration:0.75s
|
||||
rn-animationIterationCount:infinite
|
||||
rn-animationName:rn-ActivityIndicator-animation
|
||||
rn-animationPlayState:paused
|
||||
rn-animationTimingFunction:linear
|
||||
rn-backgroundColor:transparent
|
||||
rn-borderTopStyle:solid
|
||||
rn-borderRightStyle:solid
|
||||
rn-borderBottomStyle:solid
|
||||
rn-borderLeftStyle:solid
|
||||
rn-borderTopWidth:0px
|
||||
rn-borderRightWidth:0px
|
||||
rn-borderBottomWidth:0px
|
||||
rn-borderLeftWidth:0px
|
||||
rn-boxSizing:border-box
|
||||
rn-color:inherit
|
||||
rn-display:flex
|
||||
rn-flexBasis:auto
|
||||
rn-flexDirection:column
|
||||
rn-flexShrink:0
|
||||
rn-font:inherit
|
||||
rn-height:36px
|
||||
rn-listStyle:none
|
||||
rn-marginTop:0px
|
||||
rn-marginRight:0px
|
||||
rn-marginBottom:0px
|
||||
rn-marginLeft:0px
|
||||
rn-minHeight:0px
|
||||
rn-minWidth:0px
|
||||
rn-paddingTop:0px
|
||||
rn-paddingRight:0px
|
||||
rn-paddingBottom:0px
|
||||
rn-paddingLeft:0px
|
||||
rn-position:relative
|
||||
rn-textAlign:inherit
|
||||
rn-textDecoration:none
|
||||
rn-width:36px"
|
||||
style={Object {}}>
|
||||
<svg
|
||||
height="100%"
|
||||
viewBox="0 0 32 32"
|
||||
width="100%">
|
||||
<circle
|
||||
cx="16"
|
||||
cy="16"
|
||||
fill="none"
|
||||
r="14"
|
||||
strokeWidth="4"
|
||||
style={
|
||||
Object {
|
||||
"opacity": 0.2,
|
||||
"stroke": "#1976D2",
|
||||
}
|
||||
} />
|
||||
<circle
|
||||
cx="16"
|
||||
cy="16"
|
||||
fill="none"
|
||||
r="14"
|
||||
strokeWidth="4"
|
||||
style={
|
||||
Object {
|
||||
"stroke": "#1976D2",
|
||||
"strokeDasharray": 80,
|
||||
"strokeDashoffset": 60,
|
||||
}
|
||||
} />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -1,5 +1,17 @@
|
||||
/* eslint-env jasmine, jest */
|
||||
|
||||
import ActivityIndicator from '..';
|
||||
import React from 'react';
|
||||
import renderer from 'react-test-renderer';
|
||||
|
||||
describe('components/ActivityIndicator', () => {
|
||||
test.skip('NO TEST COVERAGE', () => {});
|
||||
test('default render', () => {
|
||||
const component = renderer.create(<ActivityIndicator />);
|
||||
expect(component.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('other render', () => {
|
||||
const component = renderer.create(<ActivityIndicator animating={false} hidesWhenStopped={false} size='large' />);
|
||||
expect(component.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
import Animated from '../../apis/Animated';
|
||||
import applyNativeMethods from '../../modules/applyNativeMethods';
|
||||
import Easing from 'animated/lib/Easing';
|
||||
import StyleSheet from '../../apis/StyleSheet';
|
||||
import View from '../View';
|
||||
import ViewPropTypes from '../View/ViewPropTypes';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
|
||||
const rotationInterpolation = { inputRange: [ 0, 1 ], outputRange: [ '0deg', '360deg' ] };
|
||||
|
||||
class ActivityIndicator extends Component {
|
||||
static displayName = 'ActivityIndicator';
|
||||
|
||||
static propTypes = {
|
||||
...View.propTypes,
|
||||
...ViewPropTypes,
|
||||
animating: PropTypes.bool,
|
||||
color: PropTypes.string,
|
||||
hidesWhenStopped: PropTypes.bool,
|
||||
@@ -25,21 +22,6 @@ class ActivityIndicator extends Component {
|
||||
size: 'small'
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
animation: new Animated.Value(0)
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._manageAnimation();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this._manageAnimation();
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
animating,
|
||||
@@ -50,8 +32,6 @@ class ActivityIndicator extends Component {
|
||||
...other
|
||||
} = this.props;
|
||||
|
||||
const { animation } = this.state;
|
||||
|
||||
const svg = (
|
||||
<svg height='100%' viewBox='0 0 32 32' width='100%'>
|
||||
<circle
|
||||
@@ -88,47 +68,21 @@ class ActivityIndicator extends Component {
|
||||
style={[
|
||||
styles.container,
|
||||
style,
|
||||
size && { height: size, width: size }
|
||||
typeof size === 'number' && { height: size, width: size }
|
||||
]}
|
||||
>
|
||||
<Animated.View
|
||||
<View
|
||||
children={svg}
|
||||
style={[
|
||||
indicatorStyles[size],
|
||||
hidesWhenStopped && !animating && styles.hidesWhenStopped,
|
||||
{
|
||||
transform: [
|
||||
{ rotate: animation.interpolate(rotationInterpolation) }
|
||||
]
|
||||
}
|
||||
indicatorSizes[size],
|
||||
styles.animation,
|
||||
!animating && styles.animationPause,
|
||||
!animating && hidesWhenStopped && styles.hidesWhenStopped
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
_manageAnimation() {
|
||||
const { animation } = this.state;
|
||||
|
||||
const cycleAnimation = () => {
|
||||
animation.setValue(0);
|
||||
Animated.timing(animation, {
|
||||
duration: 750,
|
||||
easing: Easing.inOut(Easing.linear),
|
||||
toValue: 1
|
||||
}).start((event) => {
|
||||
if (event.finished) {
|
||||
cycleAnimation();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (this.props.animating) {
|
||||
cycleAnimation();
|
||||
} else {
|
||||
animation.stopAnimation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
@@ -138,10 +92,19 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
hidesWhenStopped: {
|
||||
visibility: 'hidden'
|
||||
},
|
||||
animation: {
|
||||
animationDuration: '0.75s',
|
||||
animationName: 'rn-ActivityIndicator-animation',
|
||||
animationTimingFunction: 'linear',
|
||||
animationIterationCount: 'infinite'
|
||||
},
|
||||
animationPause: {
|
||||
animationPlayState: 'paused'
|
||||
}
|
||||
});
|
||||
|
||||
const indicatorStyles = StyleSheet.create({
|
||||
const indicatorSizes = StyleSheet.create({
|
||||
small: {
|
||||
width: 20,
|
||||
height: 20
|
||||
|
||||
5
src/components/Button/__tests__/index-test.js
Normal file
5
src/components/Button/__tests__/index-test.js
Normal file
@@ -0,0 +1,5 @@
|
||||
/* eslint-env jasmine, jest */
|
||||
|
||||
describe('components/Button', () => {
|
||||
test.skip('NO TEST COVERAGE', () => {});
|
||||
});
|
||||
66
src/components/Button/index.js
Normal file
66
src/components/Button/index.js
Normal file
@@ -0,0 +1,66 @@
|
||||
import ColorPropType from '../../propTypes/ColorPropType';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import StyleSheet from '../../apis/StyleSheet';
|
||||
import TouchableOpacity from '../Touchable/TouchableOpacity';
|
||||
import Text from '../Text';
|
||||
|
||||
class Button extends Component {
|
||||
static propTypes = {
|
||||
accessibilityLabel: PropTypes.string,
|
||||
color: ColorPropType,
|
||||
disabled: PropTypes.bool,
|
||||
onPress: PropTypes.func.isRequired,
|
||||
title: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
accessibilityLabel,
|
||||
color,
|
||||
disabled,
|
||||
onPress,
|
||||
title
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
accessibilityLabel={accessibilityLabel}
|
||||
accessibilityRole={'button'}
|
||||
disabled={disabled}
|
||||
onPress={onPress}
|
||||
style={[
|
||||
styles.button,
|
||||
color && { backgroundColor: color },
|
||||
disabled && styles.buttonDisabled
|
||||
]}>
|
||||
<Text style={[
|
||||
styles.text,
|
||||
disabled && styles.textDisabled
|
||||
]}>
|
||||
{title}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
button: {
|
||||
backgroundColor: '#2196F3',
|
||||
borderRadius: 2
|
||||
},
|
||||
text: {
|
||||
textAlign: 'center',
|
||||
color: '#fff',
|
||||
padding: 8,
|
||||
fontWeight: '500'
|
||||
},
|
||||
buttonDisabled: {
|
||||
backgroundColor: '#dfdfdf'
|
||||
},
|
||||
textDisabled: {
|
||||
color: '#a1a1a1'
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Button;
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user