mirror of
https://github.com/zhigang1992/react-native-web.git
synced 2026-03-30 23:23:35 +08:00
Compare commits
56 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
424e6b5994 | ||
|
|
769931061a | ||
|
|
7aa760506a | ||
|
|
9401eb9b47 | ||
|
|
6a9212df40 | ||
|
|
f2772b89bf | ||
|
|
9e970b3c34 | ||
|
|
9677e9da0a | ||
|
|
2440e74e99 | ||
|
|
66b0387023 | ||
|
|
94f37740af | ||
|
|
e1991f8f6b | ||
|
|
21eeafabd5 | ||
|
|
249f157ed9 | ||
|
|
0f8cff6124 | ||
|
|
30bf00a3bc | ||
|
|
f4515a3995 | ||
|
|
17b30aceb2 | ||
|
|
5f3f4db7a6 | ||
|
|
eb8aa0a9db | ||
|
|
af60504ca4 | ||
|
|
41159bcb10 | ||
|
|
640e41dc34 | ||
|
|
c609a6ff2b | ||
|
|
294d94d869 | ||
|
|
179d624917 | ||
|
|
61860b6d49 | ||
|
|
597fcc65e8 | ||
|
|
5e1e0ec8e5 | ||
|
|
0ac243038f | ||
|
|
c9d68fe93e | ||
|
|
77f72aa129 | ||
|
|
216885406f | ||
|
|
f15bf2664a | ||
|
|
79998e0acc | ||
|
|
44fc48f7a0 | ||
|
|
37f2d78f34 | ||
|
|
1dc769bfb1 | ||
|
|
4b3cb41107 | ||
|
|
ed2cbfd5d3 | ||
|
|
8c4b5b68c3 | ||
|
|
3564bbf840 | ||
|
|
297b2e5afb | ||
|
|
215697234e | ||
|
|
9efa7e94bd | ||
|
|
c44da41497 | ||
|
|
331c92fb3a | ||
|
|
26758e905c | ||
|
|
a15b15c55d | ||
|
|
f0202dbe61 | ||
|
|
d4d67dafc0 | ||
|
|
579bdeb8a5 | ||
|
|
7132a18440 | ||
|
|
18881b1edb | ||
|
|
4d1e7d8c0b | ||
|
|
a7158aeb6f |
7
.babelrc
7
.babelrc
@@ -1,10 +1,5 @@
|
||||
{
|
||||
"presets": [
|
||||
"es2015",
|
||||
"stage-1",
|
||||
"react"
|
||||
],
|
||||
"plugins": [
|
||||
"transform-decorators-legacy"
|
||||
"react-native"
|
||||
]
|
||||
}
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
/dist
|
||||
/node_modules
|
||||
/storybook
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[![Build Status][travis-image]][travis-url]
|
||||
[![npm version][npm-image]][npm-url]
|
||||

|
||||

|
||||
|
||||
[React Native][react-native-url] components and APIs for the Web.
|
||||
|
||||
@@ -102,7 +102,6 @@ Exported modules:
|
||||
* [`ActivityIndicator`](docs/components/ActivityIndicator.md)
|
||||
* [`Image`](docs/components/Image.md)
|
||||
* [`ListView`](docs/components/ListView.md)
|
||||
* [`Portal`](docs/components/Portal.md)
|
||||
* [`ScrollView`](docs/components/ScrollView.md)
|
||||
* [`Text`](docs/components/Text.md)
|
||||
* [`TextInput`](docs/components/TextInput.md)
|
||||
@@ -122,6 +121,7 @@ Exported modules:
|
||||
* [`PixelRatio`](docs/apis/PixelRatio.md)
|
||||
* [`Platform`](docs/apis/Platform.md)
|
||||
* [`StyleSheet`](docs/apis/StyleSheet.md)
|
||||
* [`Vibration`](docs/apis/Vibration.md)
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ const styles = StyleSheet.create({
|
||||
|
||||
## Methods
|
||||
|
||||
**select**: any
|
||||
**select**(object): any
|
||||
|
||||
`Platform.select` takes an object containing `Platform.OS` as keys and returns
|
||||
the value for the platform you are currently running on.
|
||||
|
||||
@@ -15,17 +15,52 @@ Each key of the object passed to `create` must define a style object.
|
||||
|
||||
Flattens an array of styles into a single style object.
|
||||
|
||||
**renderToString**: function
|
||||
**render**: function
|
||||
|
||||
Returns a string of CSS used to style the application.
|
||||
Returns a React `<style>` element for use in server-side rendering.
|
||||
|
||||
## Properties
|
||||
|
||||
**absoluteFill**: number
|
||||
|
||||
A very common pattern is to create overlays with position absolute and zero positioning,
|
||||
so `absoluteFill` can be used for convenience and to reduce duplication of these repeated
|
||||
styles.
|
||||
|
||||
```js
|
||||
<View style={StyleSheet.absoluteFill} />
|
||||
```
|
||||
|
||||
**absoluteFillObject**: object
|
||||
|
||||
Sometimes you may want `absoluteFill` but with a couple tweaks - `absoluteFillObject` can be
|
||||
used to create a customized entry in a `StyleSheet`, e.g.:
|
||||
|
||||
```js
|
||||
const styles = StyleSheet.create({
|
||||
wrapper: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
backgroundColor: 'transparent',
|
||||
top: 10
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
**hairlineWidth**: number
|
||||
|
||||
## Example
|
||||
|
||||
```js
|
||||
<View style={styles.container}>
|
||||
<Text
|
||||
children={'Title text'}
|
||||
style={[
|
||||
styles.title,
|
||||
this.props.isActive && styles.activeTitle
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
borderRadius: 4,
|
||||
@@ -41,29 +76,3 @@ const styles = StyleSheet.create({
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Use styles:
|
||||
|
||||
```js
|
||||
<View style={styles.container}>
|
||||
<Text
|
||||
style={[
|
||||
styles.title,
|
||||
this.props.isActive && styles.activeTitle
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
```
|
||||
|
||||
Or:
|
||||
|
||||
```js
|
||||
<View style={styles.container}>
|
||||
<Text
|
||||
style={{
|
||||
...styles.title,
|
||||
...(this.props.isActive && styles.activeTitle)
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
```
|
||||
|
||||
35
docs/apis/Vibration.md
Normal file
35
docs/apis/Vibration.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Vibration
|
||||
|
||||
Vibration is described as a pattern of on-off pulses, which may be of varying
|
||||
lengths. The pattern may consist of either a single integer, describing the
|
||||
number of milliseconds to vibrate, or an array of integers describing a pattern
|
||||
of vibrations and pauses. Vibration is controlled with a single method:
|
||||
`Vibration.vibrate()`.
|
||||
|
||||
The vibration is asynchronous so this method will return immediately. There
|
||||
will be no effect on devices that do not support vibration.
|
||||
|
||||
## Methods
|
||||
|
||||
static **cancel**()
|
||||
|
||||
Stop the vibration.
|
||||
|
||||
static **vibrate**(pattern)
|
||||
|
||||
Start the vibration pattern.
|
||||
|
||||
## Examples
|
||||
|
||||
Vibrate once for 200ms:
|
||||
|
||||
```js
|
||||
Vibration.vibrate(200);
|
||||
Vibration.vibrate([200]);
|
||||
```
|
||||
|
||||
Vibrate for 200ms, pause for 100ms, vibrate for 200ms:
|
||||
|
||||
```js
|
||||
Vibration.vibrate([200, 100, 200]);
|
||||
```
|
||||
@@ -31,7 +31,8 @@ Invoked on load error with `{nativeEvent: {error}}`.
|
||||
|
||||
**onLayout**: function
|
||||
|
||||
TODO
|
||||
Invoked on mount and layout changes with `{ nativeEvent: { layout: { x, y, width,
|
||||
height } } }`, where `x` and `y` are the offsets from the parent node.
|
||||
|
||||
**onLoad**: function
|
||||
|
||||
@@ -45,7 +46,7 @@ Invoked when load either succeeds or fails,
|
||||
|
||||
Invoked on load start.
|
||||
|
||||
**resizeMode**: oneOf('contain', 'cover', 'none', 'stretch') = 'stretch'
|
||||
**resizeMode**: oneOf('center', 'contain', 'cover', 'none', 'repeat', 'stretch') = 'stretch'
|
||||
|
||||
Determines how to resize the image when the frame doesn't match the raw image
|
||||
dimensions.
|
||||
@@ -57,7 +58,7 @@ could be an http address or a base64 encoded image.
|
||||
|
||||
**style**: style
|
||||
|
||||
+ ...[View#style](View.md)
|
||||
+ ...[View#style](./View.md)
|
||||
+ `resizeMode`
|
||||
|
||||
**testID**: string
|
||||
|
||||
@@ -4,6 +4,8 @@ TODO
|
||||
|
||||
## Props
|
||||
|
||||
[...ScrollView props](./ScrollView.md)
|
||||
|
||||
**children**: any
|
||||
|
||||
Content to display over the image.
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
# Portal
|
||||
|
||||
`Portal` is used to render modal content on top of everything else in the
|
||||
application. It passes modal views all the way up to the root element created
|
||||
by `AppRegistry.runApplication`.
|
||||
|
||||
There can only be one `Portal` instance rendered in an application, and this
|
||||
instance is controlled by React Native for Web.
|
||||
|
||||
## Methods
|
||||
|
||||
static **allocateTag**()
|
||||
|
||||
Creates a new unique tag for the modal that your component is rendering. A
|
||||
good place to allocate a tag is in `componentWillMount`. Returns a string. See
|
||||
`showModal` and `closeModal`.
|
||||
|
||||
static **closeModal**(tag: string)
|
||||
|
||||
Remove a modal from the collection of modals to be rendered. The `tag` must
|
||||
exactly match the tag previous passed to `showModal` to identify the React
|
||||
component.
|
||||
|
||||
static **getOpenModals**()
|
||||
|
||||
Get an array of all the open modals, as identified by their tag string.
|
||||
|
||||
static **showModal**(tag: string, component: any)
|
||||
|
||||
Render a new modal. The `tag` must be unique as it is used to identify the
|
||||
React component to render. This same tag can later be used in `closeModal`.
|
||||
|
||||
## Examples
|
||||
|
||||
```js
|
||||
import React, { Component } from 'react'
|
||||
import { Portal, Text, Touchable } from 'react-native'
|
||||
|
||||
export default class PortalExample extends Component {
|
||||
componentWillMount() {
|
||||
this._portalTag = Portal.allocateTag()
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Touchable onPress={this._handlePortalOpen.bind(this)}>
|
||||
<Text>Open portal</Text>
|
||||
</Touchable>
|
||||
)
|
||||
}
|
||||
|
||||
_handlePortalClose(e) {
|
||||
Portal.closeModal(this._portalTag)
|
||||
}
|
||||
|
||||
_handlePortalOpen(e) {
|
||||
Portal.showModal(this._portalTag, this._renderPortalContent())
|
||||
}
|
||||
|
||||
_renderPortalContent() {
|
||||
return (
|
||||
<Touchable onPress={this._handlePortalClose.bind(this)}>
|
||||
<Text>Close portal</Text>
|
||||
</Touchable>
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -29,8 +29,6 @@ Determines whether the keyboard gets dismissed in response to a scroll drag.
|
||||
|
||||
**onContentSizeChange**: function
|
||||
|
||||
TODO
|
||||
|
||||
Called when scrollable content view of the `ScrollView` changes. It's
|
||||
implemented using the `onLayout` handler attached to the content container
|
||||
which this `ScrollView` renders.
|
||||
|
||||
@@ -32,7 +32,7 @@ Note: Avoid changing `accessibilityRole` values over time or after user
|
||||
actions. Generally, accessibility APIs do not provide a means of notifying
|
||||
assistive technologies of a `role` value change.
|
||||
|
||||
(web) **accessible**: bool = true
|
||||
**accessible**: bool = true
|
||||
|
||||
When `false`, the text is hidden from assistive technologies. (This is
|
||||
implemented using `aria-hidden`.)
|
||||
@@ -45,10 +45,19 @@ Child content.
|
||||
|
||||
Truncates the text with an ellipsis after this many lines. Currently only supports `1`.
|
||||
|
||||
**onLayout**: function
|
||||
|
||||
Invoked on mount and layout changes with `{ nativeEvent: { layout: { x, y, width,
|
||||
height } } }`, where `x` and `y` are the offsets from the parent node.
|
||||
|
||||
**onPress**: function
|
||||
|
||||
This function is called on press.
|
||||
|
||||
**selectable**: bool = true
|
||||
|
||||
Lets the user select the text.
|
||||
|
||||
**style**: style
|
||||
|
||||
+ ...[View#style](View.md)
|
||||
|
||||
@@ -14,16 +14,11 @@ Unsupported React Native props:
|
||||
`enablesReturnKeyAutomatically` (ios),
|
||||
`returnKeyType` (ios),
|
||||
`selectionState` (ios),
|
||||
`textAlign` (android),
|
||||
`textAlignVertical` (android),
|
||||
`underlineColorAndroid` (android)
|
||||
|
||||
## Props
|
||||
|
||||
(web) **accessibilityLabel**: string
|
||||
|
||||
Defines the text label available to assistive technologies upon interaction
|
||||
with the element. (This is implemented using `aria-label`.)
|
||||
[...View props](./View.md)
|
||||
|
||||
(web) **autoComplete**: bool = false
|
||||
|
||||
@@ -92,10 +87,6 @@ as an argument to the callback handler.
|
||||
|
||||
Callback that is called when the text input is focused.
|
||||
|
||||
**onLayout**: function
|
||||
|
||||
TODO
|
||||
|
||||
(web) **onSelectionChange**: function
|
||||
|
||||
Callback that is called when the text input's selection changes. The following
|
||||
@@ -132,7 +123,7 @@ If `true`, all text will automatically be selected on focus.
|
||||
|
||||
**style**: style
|
||||
|
||||
+ ...[Text#style](Text.md)
|
||||
+ ...[Text#style](./Text.md)
|
||||
+ `outline`
|
||||
|
||||
**testID**: string
|
||||
|
||||
@@ -9,6 +9,8 @@ several child components, wrap them in a View.
|
||||
|
||||
## Props
|
||||
|
||||
[...View props](./View.md)
|
||||
|
||||
**accessibilityLabel**: string
|
||||
|
||||
Overrides the text that's read by the screen reader when the user interacts
|
||||
@@ -22,6 +24,8 @@ Allows assistive technologies to present and support interaction with the view
|
||||
|
||||
When `false`, the view is hidden from screenreaders.
|
||||
|
||||
**children**: View
|
||||
|
||||
**delayLongPress**: number
|
||||
|
||||
Delay in ms, from `onPressIn`, before `onLongPress` is called.
|
||||
@@ -47,9 +51,8 @@ always takes precedence if a touch hits two overlapping views.
|
||||
|
||||
**onLayout**: function
|
||||
|
||||
Invoked on mount and layout changes with.
|
||||
|
||||
`{nativeEvent: {layout: {x, y, width, height}}}`
|
||||
Invoked on mount and layout changes with `{ nativeEvent: { layout: { x, y, width,
|
||||
height } } }`, where `x` and `y` are the offsets from the parent node.
|
||||
|
||||
**onLongPress**: function
|
||||
|
||||
|
||||
@@ -48,7 +48,8 @@ implemented using `aria-hidden`.)
|
||||
|
||||
**onLayout**: function
|
||||
|
||||
(TODO)
|
||||
Invoked on mount and layout changes with `{ nativeEvent: { layout: { x, y, width,
|
||||
height } } }`, where `x` and `y` are the offsets from the parent node.
|
||||
|
||||
**onMoveShouldSetResponder**: function
|
||||
|
||||
|
||||
@@ -54,5 +54,5 @@ AppRegistry.runApplication('App', {
|
||||
})
|
||||
|
||||
// prerender the app
|
||||
const { html, style, styleElement } = AppRegistry.prerenderApplication('App', { initialProps })
|
||||
const { html, styleElement } = AppRegistry.prerenderApplication('App', { initialProps })
|
||||
```
|
||||
|
||||
12
examples/.storybook/config.js
Normal file
12
examples/.storybook/config.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import { configure, addDecorator } from '@kadira/storybook'
|
||||
import centered from './decorator-centered'
|
||||
|
||||
const context = require.context('../', true, /Example\.js$/)
|
||||
|
||||
addDecorator(centered)
|
||||
|
||||
function loadStories() {
|
||||
context.keys().forEach(context)
|
||||
}
|
||||
|
||||
configure(loadStories, module)
|
||||
18
examples/.storybook/decorator-centered.js
Normal file
18
examples/.storybook/decorator-centered.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
import { StyleSheet, View } from 'react-native'
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
alignItems: 'center',
|
||||
height: '100vh',
|
||||
justifyContent: 'center'
|
||||
}
|
||||
});
|
||||
|
||||
export default function (renderStory) {
|
||||
return (
|
||||
<View style={[ StyleSheet.absoluteFill, styles.root ]}>
|
||||
{renderStory()}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -1,15 +1,7 @@
|
||||
const path = require('path')
|
||||
const webpack = require('webpack')
|
||||
|
||||
const EXAMPLES_DIRECTORY = __dirname
|
||||
|
||||
module.exports = {
|
||||
devServer: {
|
||||
contentBase: EXAMPLES_DIRECTORY
|
||||
},
|
||||
entry: {
|
||||
example: EXAMPLES_DIRECTORY
|
||||
},
|
||||
module: {
|
||||
loaders: [
|
||||
{
|
||||
@@ -17,27 +9,28 @@ module.exports = {
|
||||
exclude: /node_modules/,
|
||||
loader: 'babel-loader',
|
||||
query: { cacheDirectory: true }
|
||||
},
|
||||
{
|
||||
test: /\.(gif|jpe?g|png|svg)$/,
|
||||
loader: 'url-loader',
|
||||
query: { name: '[name].[ext]' }
|
||||
}
|
||||
]
|
||||
},
|
||||
output: {
|
||||
filename: 'bundle.js'
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
|
||||
}),
|
||||
new webpack.optimize.DedupePlugin(),
|
||||
// https://github.com/animatedjs/animated/issues/40
|
||||
new webpack.NormalModuleReplacementPlugin(
|
||||
/es6-set/,
|
||||
path.join(__dirname, '../src/modules/polyfills/Set.js')
|
||||
path.join(__dirname, '../../src/modules/polyfills/Set.js')
|
||||
),
|
||||
new webpack.optimize.OccurenceOrderPlugin()
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'react-native': path.join(__dirname, '../src')
|
||||
'react-native': path.join(__dirname, '../../src')
|
||||
}
|
||||
}
|
||||
}
|
||||
175
examples/ActivityIndicator/ActivityIndicatorExample.js
Normal file
175
examples/ActivityIndicator/ActivityIndicatorExample.js
Normal file
@@ -0,0 +1,175 @@
|
||||
import React from 'react';
|
||||
import { storiesOf, action } from '@kadira/storybook';
|
||||
import { ActivityIndicator, StyleSheet, View } from 'react-native'
|
||||
import TimerMixin from 'react-timer-mixin';
|
||||
|
||||
/**
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
* Facebook reserves all rights not expressly granted.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
||||
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
const ToggleAnimatingActivityIndicator = React.createClass({
|
||||
mixins: [TimerMixin],
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
animating: true,
|
||||
};
|
||||
},
|
||||
|
||||
setToggleTimeout() {
|
||||
this.setTimeout(() => {
|
||||
this.setState({animating: !this.state.animating});
|
||||
this.setToggleTimeout();
|
||||
}, 2000);
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
this.setToggleTimeout();
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
animating={this.state.animating}
|
||||
style={[styles.centering, {height: 80}]}
|
||||
size="large"
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const examples = [
|
||||
{
|
||||
title: 'Default (small, white)',
|
||||
render() {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
style={[styles.centering, styles.gray]}
|
||||
color="white"
|
||||
/>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Gray',
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<ActivityIndicator
|
||||
style={[styles.centering]}
|
||||
/>
|
||||
<ActivityIndicator
|
||||
style={[styles.centering, {backgroundColor: '#eeeeee'}]}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Custom colors',
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.horizontal}>
|
||||
<ActivityIndicator color="#0000ff" />
|
||||
<ActivityIndicator color="#aa00aa" />
|
||||
<ActivityIndicator color="#aa3300" />
|
||||
<ActivityIndicator color="#00aa00" />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Large',
|
||||
render() {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
style={[styles.centering, styles.gray]}
|
||||
color="white"
|
||||
size="large"
|
||||
/>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Large, custom colors',
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.horizontal}>
|
||||
<ActivityIndicator
|
||||
size="large"
|
||||
color="#0000ff"
|
||||
/>
|
||||
<ActivityIndicator
|
||||
size="large"
|
||||
color="#aa00aa"
|
||||
/>
|
||||
<ActivityIndicator
|
||||
size="large"
|
||||
color="#aa3300"
|
||||
/>
|
||||
<ActivityIndicator
|
||||
size="large"
|
||||
color="#00aa00"
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Start/stop',
|
||||
render() {
|
||||
return <ToggleAnimatingActivityIndicator />;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Custom size',
|
||||
render() {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
style={[styles.centering, {transform: [{scale: 1.5}]}]}
|
||||
size="large"
|
||||
/>
|
||||
);
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
centering: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: 8,
|
||||
},
|
||||
gray: {
|
||||
backgroundColor: '#cccccc',
|
||||
},
|
||||
horizontal: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-around',
|
||||
padding: 8,
|
||||
},
|
||||
});
|
||||
|
||||
examples.forEach((example) => {
|
||||
storiesOf('<ActivityIndicator>', module)
|
||||
.add(example.title, () => example.render())
|
||||
})
|
||||
8
examples/Game2048/Game2048Example.js
Normal file
8
examples/Game2048/Game2048Example.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
import { storiesOf, action } from '@kadira/storybook';
|
||||
import Game2048 from './Game2048'
|
||||
|
||||
storiesOf('Game2048', module)
|
||||
.add('the game', () => (
|
||||
<Game2048 />
|
||||
))
|
||||
655
examples/Image/ImageExample.js
Normal file
655
examples/Image/ImageExample.js
Normal file
@@ -0,0 +1,655 @@
|
||||
import React from 'react';
|
||||
import { storiesOf, action, addDecorator } from '@kadira/storybook';
|
||||
import { ActivityIndicator, Image, Platform, StyleSheet, Text, View } from 'react-native'
|
||||
|
||||
/**
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
* Facebook reserves all rights not expressly granted.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
||||
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
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);
|
||||
|
||||
/*
|
||||
var NetworkImageCallbackExample = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
events: [],
|
||||
startLoadPrefetched: false,
|
||||
mountTime: new Date(),
|
||||
};
|
||||
},
|
||||
|
||||
componentWillMount() {
|
||||
this.setState({mountTime: new Date()});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var { mountTime } = this.state;
|
||||
|
||||
return (
|
||||
<View>
|
||||
<Image
|
||||
source={this.props.source}
|
||||
style={[styles.base, {overflow: 'visible'}]}
|
||||
onLoadStart={() => this._loadEventFired(`✔ onLoadStart (+${new Date() - mountTime}ms)`)}
|
||||
onLoad={() => this._loadEventFired(`✔ onLoad (+${new Date() - mountTime}ms)`)}
|
||||
onLoadEnd={() => {
|
||||
this._loadEventFired(`✔ onLoadEnd (+${new Date() - mountTime}ms)`);
|
||||
this.setState({startLoadPrefetched: true}, () => {
|
||||
prefetchTask.then(() => {
|
||||
this._loadEventFired(`✔ Prefetch OK (+${new Date() - mountTime}ms)`);
|
||||
}, error => {
|
||||
this._loadEventFired(`✘ Prefetch failed (+${new Date() - mountTime}ms)`);
|
||||
});
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{this.state.startLoadPrefetched ?
|
||||
<Image
|
||||
source={this.props.prefetchedSource}
|
||||
style={[styles.base, {overflow: 'visible'}]}
|
||||
onLoadStart={() => this._loadEventFired(`✔ (prefetched) onLoadStart (+${new Date() - mountTime}ms)`)}
|
||||
onLoad={() => this._loadEventFired(`✔ (prefetched) onLoad (+${new Date() - mountTime}ms)`)}
|
||||
onLoadEnd={() => this._loadEventFired(`✔ (prefetched) onLoadEnd (+${new Date() - mountTime}ms)`)}
|
||||
/>
|
||||
: null}
|
||||
<Text style={{marginTop: 20}}>
|
||||
{this.state.events.join('\n')}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
|
||||
_loadEventFired(event) {
|
||||
this.setState((state) => {
|
||||
return state.events = [...state.events, event];
|
||||
});
|
||||
}
|
||||
});
|
||||
*/
|
||||
|
||||
var NetworkImageExample = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
error: false,
|
||||
loading: false,
|
||||
progress: 0
|
||||
};
|
||||
},
|
||||
render: function() {
|
||||
var loader = this.state.loading ?
|
||||
<View style={styles.progress}>
|
||||
<Text>{this.state.progress}%</Text>
|
||||
<ActivityIndicator style={{marginLeft:5}} />
|
||||
</View> : null;
|
||||
return this.state.error ?
|
||||
<Text>{this.state.error}</Text> :
|
||||
<Image
|
||||
source={this.props.source}
|
||||
style={[styles.base, {overflow: 'visible'}]}
|
||||
onLoadStart={(e) => this.setState({loading: true})}
|
||||
onError={(e) => this.setState({error: e.nativeEvent.error, loading: false})}
|
||||
onProgress={(e) => this.setState({progress: Math.round(100 * e.nativeEvent.loaded / e.nativeEvent.total)})}
|
||||
onLoad={() => this.setState({loading: false, error: false})}>
|
||||
{loader}
|
||||
</Image>;
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
var ImageSizeExample = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
width: 0,
|
||||
height: 0,
|
||||
};
|
||||
},
|
||||
componentDidMount: function() {
|
||||
Image.getSize(this.props.source.uri, (width, height) => {
|
||||
this.setState({width, height});
|
||||
});
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<View style={{flexDirection: 'row'}}>
|
||||
<Image
|
||||
style={{
|
||||
width: 60,
|
||||
height: 60,
|
||||
backgroundColor: 'transparent',
|
||||
marginRight: 10,
|
||||
}}
|
||||
source={this.props.source} />
|
||||
<Text>
|
||||
Actual dimensions:{'\n'}
|
||||
Width: {this.state.width}, Height: {this.state.height}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
});
|
||||
*/
|
||||
/*
|
||||
var MultipleSourcesExample = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
width: 30,
|
||||
height: 30,
|
||||
};
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={{flexDirection: 'row', justifyContent: 'space-between'}}>
|
||||
<Text
|
||||
style={styles.touchableText}
|
||||
onPress={this.decreaseImageSize} >
|
||||
Decrease image size
|
||||
</Text>
|
||||
<Text
|
||||
style={styles.touchableText}
|
||||
onPress={this.increaseImageSize} >
|
||||
Increase image size
|
||||
</Text>
|
||||
</View>
|
||||
<Text>Container image size: {this.state.width}x{this.state.height} </Text>
|
||||
<View
|
||||
style={[styles.imageContainer, {height: this.state.height, width: this.state.width}]} >
|
||||
<Image
|
||||
style={{flex: 1}}
|
||||
source={[
|
||||
{uri: 'http://facebook.github.io/react/img/logo_small.png', width: 38, height: 38},
|
||||
{uri: 'http://facebook.github.io/react/img/logo_small_2x.png', width: 76, height: 76},
|
||||
{uri: 'http://facebook.github.io/react/img/logo_og.png', width: 400, height: 400}
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
increaseImageSize: function() {
|
||||
if (this.state.width >= 100) {
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
width: this.state.width + 10,
|
||||
height: this.state.height + 10,
|
||||
});
|
||||
},
|
||||
decreaseImageSize: function() {
|
||||
if (this.state.width <= 10) {
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
width: this.state.width - 10,
|
||||
height: this.state.height - 10,
|
||||
});
|
||||
},
|
||||
});
|
||||
*/
|
||||
|
||||
const examples = [
|
||||
{
|
||||
title: 'Plain Network Image',
|
||||
description: 'If the `source` prop `uri` property is prefixed with ' +
|
||||
'"http", then it will be downloaded from the network.',
|
||||
render: function() {
|
||||
return (
|
||||
<Image
|
||||
source={{uri: 'http://facebook.github.io/react/img/logo_og.png'}}
|
||||
style={styles.base}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Plain Static Image',
|
||||
description: 'Static assets should be placed in the source code tree, and ' +
|
||||
'required in the same way as JavaScript modules.',
|
||||
render: function() {
|
||||
return (
|
||||
<View style={styles.horizontal}>
|
||||
<Image source={require('./uie_thumb_normal@2x.png')} style={styles.icon} />
|
||||
<Image source={require('./uie_thumb_selected@2x.png')} style={styles.icon} />
|
||||
{/*<Image source={require('./uie_comment_normal.png')} style={styles.icon} />*/}
|
||||
{/*<Image source={require('./uie_comment_highlighted.png')} style={styles.icon} />*/}
|
||||
</View>
|
||||
);
|
||||
},
|
||||
},
|
||||
/*
|
||||
{
|
||||
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}}/>
|
||||
);
|
||||
},
|
||||
},
|
||||
*/
|
||||
{
|
||||
title: 'Error Handler',
|
||||
render: function() {
|
||||
return (
|
||||
<NetworkImageExample source={{uri: 'http://TYPO_ERROR_facebook.github.io/react/img/logo_og.png'}} />
|
||||
);
|
||||
},
|
||||
platform: 'ios',
|
||||
},
|
||||
{
|
||||
title: 'Image Download Progress',
|
||||
render: function() {
|
||||
return (
|
||||
<NetworkImageExample source={{uri: 'http://facebook.github.io/origami/public/images/blog-hero.jpg?r=1'}}/>
|
||||
);
|
||||
},
|
||||
platform: 'ios',
|
||||
},
|
||||
{
|
||||
title: 'defaultSource',
|
||||
description: 'Show a placeholder image when a network image is loading',
|
||||
render: function() {
|
||||
return (
|
||||
<Image
|
||||
defaultSource={require('./bunny.png')}
|
||||
source={{uri: 'http://facebook.github.io/origami/public/images/birds.jpg'}}
|
||||
style={styles.base}
|
||||
/>
|
||||
);
|
||||
},
|
||||
platform: 'ios',
|
||||
},
|
||||
{
|
||||
title: 'Border Color',
|
||||
render: function() {
|
||||
return (
|
||||
<View style={styles.horizontal}>
|
||||
<Image
|
||||
source={smallImage}
|
||||
style={[
|
||||
styles.base,
|
||||
styles.background,
|
||||
{borderWidth: 3, borderColor: '#f099f0'}
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Border Width',
|
||||
render: function() {
|
||||
return (
|
||||
<View style={styles.horizontal}>
|
||||
<Image
|
||||
source={smallImage}
|
||||
style={[
|
||||
styles.base,
|
||||
styles.background,
|
||||
{borderWidth: 5, borderColor: '#f099f0'}
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Border Radius',
|
||||
render: function() {
|
||||
return (
|
||||
<View style={styles.horizontal}>
|
||||
<Image
|
||||
style={[styles.base, {borderRadius: 5}]}
|
||||
source={fullImage}
|
||||
/>
|
||||
<Image
|
||||
style={[styles.base, styles.leftMargin, {borderRadius: 19}]}
|
||||
source={fullImage}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Background Color',
|
||||
render: function() {
|
||||
return (
|
||||
<View style={styles.horizontal}>
|
||||
<Image source={smallImage} style={styles.base} />
|
||||
<Image
|
||||
style={[
|
||||
styles.base,
|
||||
styles.leftMargin,
|
||||
{backgroundColor: 'rgba(0, 0, 100, 0.25)'}
|
||||
]}
|
||||
source={smallImage}
|
||||
/>
|
||||
<Image
|
||||
style={[styles.base, styles.leftMargin, {backgroundColor: 'red'}]}
|
||||
source={smallImage}
|
||||
/>
|
||||
<Image
|
||||
style={[styles.base, styles.leftMargin, {backgroundColor: 'black'}]}
|
||||
source={smallImage}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Opacity',
|
||||
render: function() {
|
||||
return (
|
||||
<View style={styles.horizontal}>
|
||||
<Image
|
||||
style={[styles.base, {opacity: 1}]}
|
||||
source={fullImage}
|
||||
/>
|
||||
<Image
|
||||
style={[styles.base, styles.leftMargin, {opacity: 0.8}]}
|
||||
source={fullImage}
|
||||
/>
|
||||
<Image
|
||||
style={[styles.base, styles.leftMargin, {opacity: 0.6}]}
|
||||
source={fullImage}
|
||||
/>
|
||||
<Image
|
||||
style={[styles.base, styles.leftMargin, {opacity: 0.4}]}
|
||||
source={fullImage}
|
||||
/>
|
||||
<Image
|
||||
style={[styles.base, styles.leftMargin, {opacity: 0.2}]}
|
||||
source={fullImage}
|
||||
/>
|
||||
<Image
|
||||
style={[styles.base, styles.leftMargin, {opacity: 0}]}
|
||||
source={fullImage}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Nesting',
|
||||
render: function() {
|
||||
return (
|
||||
<Image
|
||||
style={{width: 60, height: 60, backgroundColor: 'transparent'}}
|
||||
source={fullImage}>
|
||||
<Text style={styles.nestedText}>
|
||||
React
|
||||
</Text>
|
||||
</Image>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Tint Color',
|
||||
description: 'The `tintColor` style prop changes all the non-alpha ' +
|
||||
'pixels to the tint color.',
|
||||
render: function() {
|
||||
return (
|
||||
<View>
|
||||
<View style={styles.horizontal}>
|
||||
<Image
|
||||
source={require('./uie_thumb_normal@2x.png')}
|
||||
style={[styles.icon, {borderRadius: 5, tintColor: '#5ac8fa' }]}
|
||||
/>
|
||||
<Image
|
||||
source={require('./uie_thumb_normal@2x.png')}
|
||||
style={[styles.icon, styles.leftMargin, {borderRadius: 5, tintColor: '#4cd964' }]}
|
||||
/>
|
||||
<Image
|
||||
source={require('./uie_thumb_normal@2x.png')}
|
||||
style={[styles.icon, styles.leftMargin, {borderRadius: 5, tintColor: '#ff2d55' }]}
|
||||
/>
|
||||
<Image
|
||||
source={require('./uie_thumb_normal@2x.png')}
|
||||
style={[styles.icon, styles.leftMargin, {borderRadius: 5, tintColor: '#8e8e93' }]}
|
||||
/>
|
||||
</View>
|
||||
<Text style={styles.sectionText}>
|
||||
It also works with downloaded images:
|
||||
</Text>
|
||||
<View style={styles.horizontal}>
|
||||
<Image
|
||||
source={smallImage}
|
||||
style={[styles.base, {borderRadius: 5, tintColor: '#5ac8fa' }]}
|
||||
/>
|
||||
<Image
|
||||
source={smallImage}
|
||||
style={[styles.base, styles.leftMargin, {borderRadius: 5, tintColor: '#4cd964' }]}
|
||||
/>
|
||||
<Image
|
||||
source={smallImage}
|
||||
style={[styles.base, styles.leftMargin, {borderRadius: 5, tintColor: '#ff2d55' }]}
|
||||
/>
|
||||
<Image
|
||||
source={smallImage}
|
||||
style={[styles.base, styles.leftMargin, {borderRadius: 5, tintColor: '#8e8e93' }]}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Resize Mode',
|
||||
description: 'The `resizeMode` style prop controls how the image is ' +
|
||||
'rendered within the frame.',
|
||||
render: function() {
|
||||
return (
|
||||
<View>
|
||||
{[smallImage, fullImage].map((image, index) => {
|
||||
return (
|
||||
<View key={index}>
|
||||
<View style={styles.horizontal}>
|
||||
<View>
|
||||
<Text style={[styles.resizeModeText]}>
|
||||
Contain
|
||||
</Text>
|
||||
<Image
|
||||
style={styles.resizeMode}
|
||||
resizeMode={Image.resizeMode.contain}
|
||||
source={image}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.leftMargin}>
|
||||
<Text style={[styles.resizeModeText]}>
|
||||
Cover
|
||||
</Text>
|
||||
<Image
|
||||
style={styles.resizeMode}
|
||||
resizeMode={Image.resizeMode.cover}
|
||||
source={image}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.horizontal}>
|
||||
<View>
|
||||
<Text style={[styles.resizeModeText]}>
|
||||
Stretch
|
||||
</Text>
|
||||
<Image
|
||||
style={styles.resizeMode}
|
||||
resizeMode={Image.resizeMode.stretch}
|
||||
source={image}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.leftMargin}>
|
||||
<Text style={[styles.resizeModeText]}>
|
||||
Repeat
|
||||
</Text>
|
||||
<Image
|
||||
style={styles.resizeMode}
|
||||
resizeMode={'repeat'}
|
||||
source={image}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.leftMargin}>
|
||||
<Text style={[styles.resizeModeText]}>
|
||||
Center
|
||||
</Text>
|
||||
<Image
|
||||
style={styles.resizeMode}
|
||||
resizeMode={'center'}
|
||||
source={image}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Animated GIF',
|
||||
render: function() {
|
||||
return (
|
||||
<Image
|
||||
style={styles.gif}
|
||||
source={{uri: 'http://38.media.tumblr.com/9e9bd08c6e2d10561dd1fb4197df4c4e/tumblr_mfqekpMktw1rn90umo1_500.gif'}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
platform: 'ios',
|
||||
},
|
||||
{
|
||||
title: 'Base64 image',
|
||||
render: function() {
|
||||
return (
|
||||
<Image
|
||||
style={styles.base64}
|
||||
source={{uri: base64Icon, scale: 3}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
platform: 'ios',
|
||||
},
|
||||
/*
|
||||
{
|
||||
title: 'Cap Insets',
|
||||
description:
|
||||
'When the image is resized, the corners of the size specified ' +
|
||||
'by capInsets will stay a fixed size, but the center content and ' +
|
||||
'borders of the image will be stretched. This is useful for creating ' +
|
||||
'resizable rounded buttons, shadows, and other resizable assets.',
|
||||
render: function() {
|
||||
return <ImageCapInsetsExample />;
|
||||
},
|
||||
platform: 'ios',
|
||||
},
|
||||
*/
|
||||
/*
|
||||
{
|
||||
title: 'Image Size',
|
||||
render: function() {
|
||||
return <ImageSizeExample source={fullImage} />;
|
||||
},
|
||||
},
|
||||
*/
|
||||
/*
|
||||
{
|
||||
title: 'MultipleSourcesExample',
|
||||
description:
|
||||
'The `source` prop allows passing in an array of uris, so that native to choose which image ' +
|
||||
'to diplay based on the size of the of the target image',
|
||||
render: function() {
|
||||
return <MultipleSourcesExample />;
|
||||
},
|
||||
platform: 'android',
|
||||
},
|
||||
*/
|
||||
];
|
||||
|
||||
var fullImage = {uri: 'http://facebook.github.io/react/img/logo_og.png'};
|
||||
var smallImage = {uri: 'http://facebook.github.io/react/img/logo_small_2x.png'};
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
base: {
|
||||
width: 38,
|
||||
height: 38,
|
||||
},
|
||||
progress: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
width: 100
|
||||
},
|
||||
leftMargin: {
|
||||
marginLeft: 10,
|
||||
},
|
||||
background: {
|
||||
backgroundColor: '#222222'
|
||||
},
|
||||
sectionText: {
|
||||
marginVertical: 6,
|
||||
},
|
||||
nestedText: {
|
||||
marginLeft: 12,
|
||||
marginTop: 20,
|
||||
backgroundColor: 'transparent',
|
||||
color: 'white'
|
||||
},
|
||||
resizeMode: {
|
||||
width: 90,
|
||||
height: 60,
|
||||
borderWidth: 0.5,
|
||||
borderColor: 'black'
|
||||
},
|
||||
resizeModeText: {
|
||||
fontSize: 11,
|
||||
marginBottom: 3,
|
||||
},
|
||||
icon: {
|
||||
width: 15,
|
||||
height: 15,
|
||||
},
|
||||
horizontal: {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
gif: {
|
||||
flex: 1,
|
||||
height: 200,
|
||||
},
|
||||
base64: {
|
||||
flex: 1,
|
||||
height: 50,
|
||||
resizeMode: 'contain',
|
||||
},
|
||||
touchableText: {
|
||||
fontWeight: '500',
|
||||
color: 'blue',
|
||||
},
|
||||
});
|
||||
|
||||
examples.forEach((example) => {
|
||||
storiesOf('<Image>', module)
|
||||
.addDecorator((renderStory) => <View>{renderStory()}</View>)
|
||||
.add(example.title, () => example.render())
|
||||
})
|
||||
BIN
examples/Image/bunny.png
Normal file
BIN
examples/Image/bunny.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
BIN
examples/Image/uie_thumb_normal@2x.png
Normal file
BIN
examples/Image/uie_thumb_normal@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 850 B |
BIN
examples/Image/uie_thumb_selected@2x.png
Normal file
BIN
examples/Image/uie_thumb_selected@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
4
examples/ListView/ListViewExample.js
Normal file
4
examples/ListView/ListViewExample.js
Normal file
@@ -0,0 +1,4 @@
|
||||
import React from 'react';
|
||||
import { storiesOf, action } from '@kadira/storybook';
|
||||
import { ListView } from 'react-native'
|
||||
|
||||
117
examples/PanResponder/PanResponderExample.js
Normal file
117
examples/PanResponder/PanResponderExample.js
Normal file
@@ -0,0 +1,117 @@
|
||||
'use strict';
|
||||
|
||||
import { storiesOf, action } from '@kadira/storybook';
|
||||
|
||||
var React = require('react');
|
||||
var ReactNative = require('react-native');
|
||||
var {
|
||||
PanResponder,
|
||||
StyleSheet,
|
||||
View
|
||||
} = ReactNative;
|
||||
|
||||
var CIRCLE_SIZE = 80;
|
||||
|
||||
var PanResponderExample = React.createClass({
|
||||
_panResponder: {},
|
||||
_previousLeft: 0,
|
||||
_previousTop: 0,
|
||||
_circleStyles: {},
|
||||
circle: (null : ?{ setNativeProps(props: Object): void }),
|
||||
|
||||
componentWillMount: function() {
|
||||
this._panResponder = PanResponder.create({
|
||||
onStartShouldSetPanResponder: this._handleStartShouldSetPanResponder,
|
||||
onMoveShouldSetPanResponder: this._handleMoveShouldSetPanResponder,
|
||||
onPanResponderGrant: this._handlePanResponderGrant,
|
||||
onPanResponderMove: this._handlePanResponderMove,
|
||||
onPanResponderRelease: this._handlePanResponderEnd,
|
||||
onPanResponderTerminate: this._handlePanResponderEnd,
|
||||
});
|
||||
this._previousLeft = 20;
|
||||
this._previousTop = 84;
|
||||
this._circleStyles = {
|
||||
style: {
|
||||
left: this._previousLeft,
|
||||
top: this._previousTop,
|
||||
backgroundColor: 'green',
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this._updateNativeStyles();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<View
|
||||
style={styles.container}>
|
||||
<View
|
||||
ref={(circle) => {
|
||||
this.circle = circle;
|
||||
}}
|
||||
style={styles.circle}
|
||||
{...this._panResponder.panHandlers}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
|
||||
_highlight: function() {
|
||||
this._circleStyles.style.backgroundColor = 'blue';
|
||||
this._updateNativeStyles();
|
||||
},
|
||||
|
||||
_unHighlight: function() {
|
||||
this._circleStyles.style.backgroundColor = 'green';
|
||||
this._updateNativeStyles();
|
||||
},
|
||||
|
||||
_updateNativeStyles: function() {
|
||||
this.circle && this.circle.setNativeProps(this._circleStyles);
|
||||
},
|
||||
|
||||
_handleStartShouldSetPanResponder: function(e: Object, gestureState: Object): boolean {
|
||||
// Should we become active when the user presses down on the circle?
|
||||
return true;
|
||||
},
|
||||
|
||||
_handleMoveShouldSetPanResponder: function(e: Object, gestureState: Object): boolean {
|
||||
// Should we become active when the user moves a touch over the circle?
|
||||
return false;
|
||||
},
|
||||
|
||||
_handlePanResponderGrant: function(e: Object, gestureState: Object) {
|
||||
this._highlight();
|
||||
},
|
||||
_handlePanResponderMove: function(e: Object, gestureState: Object) {
|
||||
this._circleStyles.style.left = this._previousLeft + gestureState.dx;
|
||||
this._circleStyles.style.top = this._previousTop + gestureState.dy;
|
||||
this._updateNativeStyles();
|
||||
},
|
||||
_handlePanResponderEnd: function(e: Object, gestureState: Object) {
|
||||
this._unHighlight();
|
||||
this._previousLeft += gestureState.dx;
|
||||
this._previousTop += gestureState.dy;
|
||||
},
|
||||
});
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
circle: {
|
||||
width: CIRCLE_SIZE,
|
||||
height: CIRCLE_SIZE,
|
||||
borderRadius: CIRCLE_SIZE / 2,
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
top: 0,
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
paddingTop: 64,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
storiesOf('PanResponder', module)
|
||||
.add('example', () => <PanResponderExample />)
|
||||
57
examples/ScrollView/ScrollViewExample.js
Normal file
57
examples/ScrollView/ScrollViewExample.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import React from 'react';
|
||||
import { storiesOf, action } from '@kadira/storybook';
|
||||
import { ScrollView, StyleSheet, Text, View } from 'react-native'
|
||||
|
||||
storiesOf('<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
|
||||
style={styles.scrollViewStyle}
|
||||
>
|
||||
{Array.from({ length: 50 }).map((item, i) => (
|
||||
<View key={i} style={styles.box}>
|
||||
<Text>{i}</Text>
|
||||
</View>
|
||||
))}
|
||||
</ScrollView>
|
||||
</View>
|
||||
))
|
||||
.add('horizontal', () => (
|
||||
<View style={styles.scrollViewContainer}>
|
||||
<ScrollView
|
||||
contentContainerStyle={styles.scrollViewContentContainerStyle}
|
||||
horizontal
|
||||
onScroll={e => console.log('ScrollView.onScroll', e)}
|
||||
scrollEventThrottle={1} // 1 event per second
|
||||
style={styles.scrollViewStyle}
|
||||
>
|
||||
{Array.from({ length: 50 }).map((item, i) => (
|
||||
<View key={i} style={[ styles.box, styles.horizontalBox ]}>
|
||||
<Text>{i}</Text>
|
||||
</View>
|
||||
))}
|
||||
</ScrollView>
|
||||
</View>
|
||||
))
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
box: {
|
||||
alignItems: 'center',
|
||||
flexGrow: 1,
|
||||
justifyContent: 'center',
|
||||
borderWidth: 1
|
||||
},
|
||||
scrollViewContainer: {
|
||||
height: '200px',
|
||||
width: 300
|
||||
},
|
||||
scrollViewStyle: {
|
||||
borderWidth: '1px'
|
||||
},
|
||||
scrollViewContentContainerStyle: {
|
||||
padding: '10px'
|
||||
}
|
||||
})
|
||||
472
examples/Text/TextExample.js
Normal file
472
examples/Text/TextExample.js
Normal file
@@ -0,0 +1,472 @@
|
||||
import React from 'react';
|
||||
import { storiesOf, action } from '@kadira/storybook';
|
||||
import { Image, StyleSheet, Text, View } from 'react-native'
|
||||
|
||||
/**
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
* Facebook reserves all rights not expressly granted.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
||||
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
var Entity = React.createClass({
|
||||
render: function() {
|
||||
return (
|
||||
<Text style={{fontWeight: '500', color: '#527fe4'}}>
|
||||
{this.props.children}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var AttributeToggler = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {fontWeight: 'bold', fontSize: 15};
|
||||
},
|
||||
toggleWeight: function() {
|
||||
this.setState({
|
||||
fontWeight: this.state.fontWeight === 'bold' ? 'normal' : 'bold'
|
||||
});
|
||||
},
|
||||
increaseSize: function() {
|
||||
this.setState({
|
||||
fontSize: this.state.fontSize + 1
|
||||
});
|
||||
},
|
||||
render: function() {
|
||||
var curStyle = {fontWeight: this.state.fontWeight, fontSize: this.state.fontSize};
|
||||
return (
|
||||
<View>
|
||||
<Text style={curStyle}>
|
||||
Tap the controls below to change attributes.
|
||||
</Text>
|
||||
<Text>
|
||||
<Text>See how it will even work on <Text style={curStyle}>this nested text</Text></Text>
|
||||
</Text>
|
||||
<Text
|
||||
style={{backgroundColor: '#ffaaaa', marginTop: 5}}
|
||||
onPress={this.toggleWeight}>
|
||||
Toggle Weight
|
||||
</Text>
|
||||
<Text
|
||||
style={{backgroundColor: '#aaaaff', marginTop: 5}}
|
||||
onPress={this.increaseSize}>
|
||||
Increase Size
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const examples = [
|
||||
{
|
||||
title: 'Wrap',
|
||||
render: function() {
|
||||
return (
|
||||
<Text>
|
||||
The text should wrap if it goes on multiple lines. See, this is going to
|
||||
the next line.
|
||||
</Text>
|
||||
);
|
||||
},
|
||||
}, {
|
||||
title: 'Padding',
|
||||
render: function() {
|
||||
return (
|
||||
<Text style={{padding: 10}}>
|
||||
This text is indented by 10px padding on all sides.
|
||||
</Text>
|
||||
);
|
||||
},
|
||||
}, {
|
||||
title: 'Font Family',
|
||||
render: function() {
|
||||
return (
|
||||
<View>
|
||||
<Text style={{fontFamily: 'Cochin'}}>
|
||||
Cochin
|
||||
</Text>
|
||||
<Text style={{fontFamily: 'Cochin', fontWeight: 'bold'}}>
|
||||
Cochin bold
|
||||
</Text>
|
||||
<Text style={{fontFamily: 'Helvetica'}}>
|
||||
Helvetica
|
||||
</Text>
|
||||
<Text style={{fontFamily: 'Helvetica', fontWeight: 'bold'}}>
|
||||
Helvetica bold
|
||||
</Text>
|
||||
<Text style={{fontFamily: 'Verdana'}}>
|
||||
Verdana
|
||||
</Text>
|
||||
<Text style={{fontFamily: 'Verdana', fontWeight: 'bold'}}>
|
||||
Verdana bold
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
}, {
|
||||
title: 'Font Size',
|
||||
render: function() {
|
||||
return (
|
||||
<View>
|
||||
<Text style={{fontSize: 23}}>
|
||||
Size 23
|
||||
</Text>
|
||||
<Text style={{fontSize: 8}}>
|
||||
Size 8
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
}, {
|
||||
title: 'Color',
|
||||
render: function() {
|
||||
return (
|
||||
<View>
|
||||
<Text style={{color: 'red'}}>
|
||||
Red color
|
||||
</Text>
|
||||
<Text style={{color: 'blue'}}>
|
||||
Blue color
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
}, {
|
||||
title: 'Font Weight',
|
||||
render: function() {
|
||||
return (
|
||||
<View>
|
||||
<Text style={{fontSize: 20, fontWeight: '100'}}>
|
||||
Move fast and be ultralight
|
||||
</Text>
|
||||
<Text style={{fontSize: 20, fontWeight: '200'}}>
|
||||
Move fast and be light
|
||||
</Text>
|
||||
<Text style={{fontSize: 20, fontWeight: 'normal'}}>
|
||||
Move fast and be normal
|
||||
</Text>
|
||||
<Text style={{fontSize: 20, fontWeight: 'bold'}}>
|
||||
Move fast and be bold
|
||||
</Text>
|
||||
<Text style={{fontSize: 20, fontWeight: '900'}}>
|
||||
Move fast and be ultrabold
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
}, {
|
||||
title: 'Font Style',
|
||||
render: function() {
|
||||
return (
|
||||
<View>
|
||||
<Text style={{fontStyle: 'normal'}}>
|
||||
Normal text
|
||||
</Text>
|
||||
<Text style={{fontStyle: 'italic'}}>
|
||||
Italic text
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
}, {
|
||||
title: 'Text Decoration',
|
||||
render: function() {
|
||||
return (
|
||||
<View>
|
||||
<Text style={{textDecorationLine: 'underline', textDecorationStyle: 'solid'}}>
|
||||
Solid underline
|
||||
</Text>
|
||||
<Text style={{textDecorationLine: 'underline', textDecorationStyle: 'double', textDecorationColor: '#ff0000'}}>
|
||||
Double underline with custom color
|
||||
</Text>
|
||||
<Text style={{textDecorationLine: 'underline', textDecorationStyle: 'dashed', textDecorationColor: '#9CDC40'}}>
|
||||
Dashed underline with custom color
|
||||
</Text>
|
||||
<Text style={{textDecorationLine: 'underline', textDecorationStyle: 'dotted', textDecorationColor: 'blue'}}>
|
||||
Dotted underline with custom color
|
||||
</Text>
|
||||
<Text style={{textDecorationLine: 'none'}}>
|
||||
None textDecoration
|
||||
</Text>
|
||||
<Text style={{textDecorationLine: 'line-through', textDecorationStyle: 'solid'}}>
|
||||
Solid line-through
|
||||
</Text>
|
||||
<Text style={{textDecorationLine: 'line-through', textDecorationStyle: 'double', textDecorationColor: '#ff0000'}}>
|
||||
Double line-through with custom color
|
||||
</Text>
|
||||
<Text style={{textDecorationLine: 'line-through', textDecorationStyle: 'dashed', textDecorationColor: '#9CDC40'}}>
|
||||
Dashed line-through with custom color
|
||||
</Text>
|
||||
<Text style={{textDecorationLine: 'line-through', textDecorationStyle: 'dotted', textDecorationColor: 'blue'}}>
|
||||
Dotted line-through with custom color
|
||||
</Text>
|
||||
<Text style={{textDecorationLine: 'underline line-through'}}>
|
||||
Both underline and line-through
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
}, {
|
||||
title: 'Nested',
|
||||
description: 'Nested text components will inherit the styles of their ' +
|
||||
'parents (only backgroundColor is inherited from non-Text parents). ' +
|
||||
'<Text> only supports other <Text> and raw text (strings) as children.',
|
||||
render: function() {
|
||||
return (
|
||||
<View>
|
||||
<Text>
|
||||
(Normal text,
|
||||
<Text style={{fontWeight: 'bold'}}>
|
||||
(and bold
|
||||
<Text style={{fontSize: 11, color: '#527fe4'}}>
|
||||
(and tiny inherited bold blue)
|
||||
</Text>
|
||||
)
|
||||
</Text>
|
||||
)
|
||||
</Text>
|
||||
<Text style={{opacity:0.7}}>
|
||||
(opacity
|
||||
<Text>
|
||||
(is inherited
|
||||
<Text style={{opacity:0.7}}>
|
||||
(and accumulated
|
||||
<Text style={{backgroundColor:'#ffaaaa'}}>
|
||||
(and also applies to the background)
|
||||
</Text>
|
||||
)
|
||||
</Text>
|
||||
)
|
||||
</Text>
|
||||
)
|
||||
</Text>
|
||||
<Text style={{fontSize: 12}}>
|
||||
<Entity>Entity Name</Entity>
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
}, {
|
||||
title: 'Text Align',
|
||||
render: function() {
|
||||
return (
|
||||
<View>
|
||||
<Text>
|
||||
auto (default) - english LTR
|
||||
</Text>
|
||||
<Text style={{ writingDirection: 'rtl' }}>
|
||||
أحب اللغة العربية auto (default) - arabic RTL
|
||||
</Text>
|
||||
<Text style={{textAlign: 'left'}}>
|
||||
left left left left left left left left left left left left left left left
|
||||
</Text>
|
||||
<Text style={{textAlign: 'center'}}>
|
||||
center center center center center center center center center center center
|
||||
</Text>
|
||||
<Text style={{textAlign: 'right'}}>
|
||||
right right right right right right right right right right right right right
|
||||
</Text>
|
||||
<Text style={{textAlign: 'justify'}}>
|
||||
justify: this text component{"'"}s contents are laid out with "textAlign: justify"
|
||||
and as you can see all of the lines except the last one span the
|
||||
available width of the parent container.
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
}, {
|
||||
title: 'Letter Spacing',
|
||||
render: function() {
|
||||
return (
|
||||
<View>
|
||||
<Text style={{letterSpacing: 0}}>
|
||||
letterSpacing = 0
|
||||
</Text>
|
||||
<Text style={{letterSpacing: 2, marginTop: 5}}>
|
||||
letterSpacing = 2
|
||||
</Text>
|
||||
<Text style={{letterSpacing: 9, marginTop: 5}}>
|
||||
letterSpacing = 9
|
||||
</Text>
|
||||
<Text style={{letterSpacing: -1, marginTop: 5}}>
|
||||
letterSpacing = -1
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
}, {
|
||||
title: 'Spaces',
|
||||
render: function() {
|
||||
return (
|
||||
<Text>
|
||||
A {'generated'} {' '} {'string'} and some spaces
|
||||
</Text>
|
||||
);
|
||||
},
|
||||
}, {
|
||||
title: 'Line Height',
|
||||
render: function() {
|
||||
return (
|
||||
<Text>
|
||||
<Text style={{lineHeight: 35}}>
|
||||
A lot of space between the lines of this long passage that should
|
||||
wrap once.
|
||||
</Text>
|
||||
</Text>
|
||||
);
|
||||
},
|
||||
}, {
|
||||
title: 'Empty Text',
|
||||
description: 'It\'s ok to have Text with zero or null children.',
|
||||
render: function() {
|
||||
return (
|
||||
<Text />
|
||||
);
|
||||
},
|
||||
}, {
|
||||
title: 'Toggling Attributes',
|
||||
render: function(): ReactElement<any> {
|
||||
return <AttributeToggler />;
|
||||
},
|
||||
}, {
|
||||
title: 'backgroundColor attribute',
|
||||
description: 'backgroundColor is inherited from all types of views.',
|
||||
render: function() {
|
||||
return (
|
||||
<Text style={{backgroundColor: 'yellow'}}>
|
||||
Yellow container background,
|
||||
<Text style={{backgroundColor: '#ffaaaa'}}>
|
||||
{' '}red background,
|
||||
<Text style={{backgroundColor: '#aaaaff'}}>
|
||||
{' '}blue background,
|
||||
<Text>
|
||||
{' '}inherited blue background,
|
||||
<Text style={{backgroundColor: '#aaffaa'}}>
|
||||
{' '}nested green background.
|
||||
</Text>
|
||||
</Text>
|
||||
</Text>
|
||||
</Text>
|
||||
</Text>
|
||||
);
|
||||
},
|
||||
}, {
|
||||
title: 'numberOfLines attribute',
|
||||
render: function() {
|
||||
return (
|
||||
<View>
|
||||
<Text numberOfLines={1}>
|
||||
Maximum of one line, no matter how much I write here. If I keep writing, it{"'"}ll just truncate after one line.
|
||||
</Text>
|
||||
<Text numberOfLines={2} style={{marginTop: 20}}>
|
||||
Maximum of two lines, no matter how much I write here. If I keep writing, it{"'"}ll just truncate after two lines.
|
||||
</Text>
|
||||
<Text style={{marginTop: 20}}>
|
||||
No maximum lines specified, no matter how much I write here. If I keep writing, it{"'"}ll just keep going and going.
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
}, {
|
||||
title: 'Text highlighting (tap the link to see highlight)',
|
||||
render: function() {
|
||||
return (
|
||||
<View>
|
||||
<Text>Lorem ipsum dolor sit amet, <Text suppressHighlighting={false} style={{backgroundColor: 'white', textDecorationLine: 'underline', color: 'blue'}} onPress={() => null}>consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud</Text> exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</Text>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
}, {
|
||||
title: 'allowFontScaling attribute',
|
||||
render: function() {
|
||||
return (
|
||||
<View>
|
||||
<Text>
|
||||
By default, text will respect Text Size accessibility setting on iOS.
|
||||
It means that all font sizes will be increased or descreased depending on the value of Text Size setting in
|
||||
{" "}<Text style={{fontWeight: 'bold'}}>Settings.app - Display & Brightness - Text Size</Text>
|
||||
</Text>
|
||||
<Text style={{marginTop: 10}}>
|
||||
You can disable scaling for your Text component by passing {"\""}allowFontScaling={"{"}false{"}\""} prop.
|
||||
</Text>
|
||||
<Text allowFontScaling={false} style={{marginTop: 20}}>
|
||||
This text will not scale.
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
}, {
|
||||
title: 'Inline views',
|
||||
render: function() {
|
||||
return (
|
||||
<View>
|
||||
<Text>
|
||||
This text contains an inline blue view <View style={{width: 25, height: 25, backgroundColor: 'steelblue'}} /> and
|
||||
an inline image <Image source={{ uri: 'http://lorempixel.com/30/11' }} style={{width: 30, height: 11, resizeMode: 'cover'}}/>. Neat, huh?
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
}, {
|
||||
title: 'Text shadow',
|
||||
render: function() {
|
||||
return (
|
||||
<View>
|
||||
<Text style={{fontSize: 20, textShadowOffset: {width: 2, height: 2}, textShadowRadius: 1, textShadowColor: '#00cccc'}}>
|
||||
Demo text shadow
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
}, {
|
||||
title: 'Line break mode',
|
||||
render: function() {
|
||||
return (
|
||||
<View>
|
||||
<Text numberOfLines={1}>
|
||||
This very long text should be truncated with dots in the end.
|
||||
</Text>
|
||||
<Text lineBreakMode="middle" numberOfLines={1}>
|
||||
This very long text should be truncated with dots in the middle.
|
||||
</Text>
|
||||
<Text lineBreakMode="head" numberOfLines={1}>
|
||||
This very long text should be truncated with dots in the beginning.
|
||||
</Text>
|
||||
<Text lineBreakMode="clip" numberOfLines={1}>
|
||||
This very looooooooooooooooooooooooooooong text should be clipped.
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
}];
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
backgroundColorText: {
|
||||
margin: 5,
|
||||
marginBottom: 0,
|
||||
backgroundColor: 'rgba(100, 100, 100, 0.3)'
|
||||
},
|
||||
});
|
||||
|
||||
examples.forEach((example) => {
|
||||
storiesOf('<Text>', module)
|
||||
.addDecorator((renderStory) => <View style={{ width: 320 }}>{renderStory()}</View>)
|
||||
.add(example.title, () => example.render())
|
||||
})
|
||||
40
examples/TextInput/TextInputExample.js
Normal file
40
examples/TextInput/TextInputExample.js
Normal file
@@ -0,0 +1,40 @@
|
||||
import React from 'react';
|
||||
import { storiesOf, action } from '@kadira/storybook';
|
||||
import { StyleSheet, TextInput, View } from 'react-native'
|
||||
|
||||
storiesOf('<TextInput>', module)
|
||||
.add('tbd', () => (
|
||||
<View>
|
||||
<TextInput
|
||||
defaultValue='Default textInput'
|
||||
keyboardType='default'
|
||||
onBlur={(e) => { console.log('TextInput.onBlur', e) }}
|
||||
onChange={(e) => { console.log('TextInput.onChange', e) }}
|
||||
onChangeText={(e) => { console.log('TextInput.onChangeText', e) }}
|
||||
onFocus={(e) => { console.log('TextInput.onFocus', e) }}
|
||||
onSelectionChange={(e) => { console.log('TextInput.onSelectionChange', e) }}
|
||||
/>
|
||||
<TextInput secureTextEntry style={styles.textInput} />
|
||||
<TextInput defaultValue='read only' editable={false} style={styles.textInput} />
|
||||
<TextInput
|
||||
style={[ styles.textInput, { flex:1, height: 60, padding: 20, fontSize: 20, textAlign: 'center' } ]}
|
||||
keyboardType='email-address' placeholder='you@domain.com' placeholderTextColor='red'
|
||||
/>
|
||||
<TextInput keyboardType='numeric' style={styles.textInput} />
|
||||
<TextInput keyboardType='phone-pad' style={styles.textInput} />
|
||||
<TextInput defaultValue='https://delete-me' keyboardType='url' placeholder='https://www.some-website.com' selectTextOnFocus style={styles.textInput} />
|
||||
<TextInput
|
||||
defaultValue='default value'
|
||||
maxNumberOfLines={10}
|
||||
multiline
|
||||
numberOfLines={5}
|
||||
style={styles.textInput}
|
||||
/>
|
||||
</View>
|
||||
))
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
textInput: {
|
||||
borderWidth: 1
|
||||
}
|
||||
})
|
||||
8
examples/TicTacToe/TicTacToeExample.js
Normal file
8
examples/TicTacToe/TicTacToeExample.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
import { storiesOf, action } from '@kadira/storybook';
|
||||
import TicTacToe from './TicTacToe'
|
||||
|
||||
storiesOf('TicTacToe', module)
|
||||
.add('the game', () => (
|
||||
<TicTacToe />
|
||||
))
|
||||
450
examples/Touchable/TouchableExample.js
Normal file
450
examples/Touchable/TouchableExample.js
Normal file
@@ -0,0 +1,450 @@
|
||||
import React from 'react';
|
||||
import { storiesOf, action } from '@kadira/storybook';
|
||||
import {
|
||||
Image,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableHighlight,
|
||||
TouchableOpacity,
|
||||
Platform,
|
||||
TouchableNativeFeedback,
|
||||
View,
|
||||
} from 'react-native'
|
||||
|
||||
/**
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
* Facebook reserves all rights not expressly granted.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
||||
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
const examples = [
|
||||
{
|
||||
title: '<TouchableHighlight>',
|
||||
description: 'TouchableHighlight works by adding an extra view with a ' +
|
||||
'black background under the single child view. This works best when the ' +
|
||||
'child view is fully opaque, although it can be made to work as a simple ' +
|
||||
'background color change as well with the activeOpacity and ' +
|
||||
'underlayColor props.',
|
||||
render: function() {
|
||||
return (
|
||||
<View>
|
||||
<View style={styles.row}>
|
||||
<TouchableHighlight
|
||||
style={styles.wrapper}
|
||||
onPress={() => console.log('stock THW image - highlight')}>
|
||||
<Image
|
||||
source={heartImage}
|
||||
style={styles.image}
|
||||
/>
|
||||
</TouchableHighlight>
|
||||
<TouchableHighlight
|
||||
style={styles.wrapper}
|
||||
activeOpacity={1}
|
||||
animationVelocity={0}
|
||||
underlayColor="rgb(210, 230, 255)"
|
||||
onPress={() => console.log('custom THW text - highlight')}>
|
||||
<View style={styles.wrapperCustom}>
|
||||
<Text style={styles.text}>
|
||||
Tap Here For Custom Highlight!
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
}, {
|
||||
title: '<Text onPress={fn}> with highlight',
|
||||
render: function(): ReactElement<any> {
|
||||
return <TextOnPressBox />;
|
||||
},
|
||||
}, {
|
||||
title: 'Touchable feedback events',
|
||||
description: '<Touchable*> components accept onPress, onPressIn, ' +
|
||||
'onPressOut, and onLongPress as props.',
|
||||
render: function(): ReactElement<any> {
|
||||
return <TouchableFeedbackEvents />;
|
||||
},
|
||||
}, {
|
||||
title: 'Touchable delay for events',
|
||||
description: '<Touchable*> components also accept delayPressIn, ' +
|
||||
'delayPressOut, and delayLongPress as props. These props impact the ' +
|
||||
'timing of feedback events.',
|
||||
render: function(): ReactElement<any> {
|
||||
return <TouchableDelayEvents />;
|
||||
},
|
||||
}, {
|
||||
title: '3D Touch / Force Touch',
|
||||
description: 'iPhone 6s and 6s plus support 3D touch, which adds a force property to touches',
|
||||
render: function(): ReactElement<any> {
|
||||
return <ForceTouchExample />;
|
||||
},
|
||||
platform: 'ios',
|
||||
}, {
|
||||
title: 'Touchable Hit Slop',
|
||||
description: '<Touchable*> components accept hitSlop prop which extends the touch area ' +
|
||||
'without changing the view bounds.',
|
||||
render: function(): ReactElement<any> {
|
||||
return <TouchableHitSlop />;
|
||||
},
|
||||
}, {
|
||||
title: 'Disabled Touchable*',
|
||||
description: '<Touchable*> components accept disabled prop which prevents ' +
|
||||
'any interaction with component',
|
||||
render: function(): ReactElement<any> {
|
||||
return <TouchableDisabled />;
|
||||
},
|
||||
}];
|
||||
|
||||
var TextOnPressBox = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
timesPressed: 0,
|
||||
};
|
||||
},
|
||||
textOnPress: function() {
|
||||
this.setState({
|
||||
timesPressed: this.state.timesPressed + 1,
|
||||
});
|
||||
},
|
||||
render: function() {
|
||||
var textLog = '';
|
||||
if (this.state.timesPressed > 1) {
|
||||
textLog = this.state.timesPressed + 'x text onPress';
|
||||
} else if (this.state.timesPressed > 0) {
|
||||
textLog = 'text onPress';
|
||||
}
|
||||
|
||||
return (
|
||||
<View>
|
||||
<Text
|
||||
style={styles.textBlock}
|
||||
onPress={this.textOnPress}>
|
||||
Text has built-in onPress handling
|
||||
</Text>
|
||||
<View style={styles.logBox}>
|
||||
<Text>
|
||||
{textLog}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var TouchableFeedbackEvents = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
eventLog: [],
|
||||
};
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<View testID="touchable_feedback_events">
|
||||
<View style={[styles.row, {justifyContent: 'center'}]}>
|
||||
<TouchableOpacity
|
||||
style={styles.wrapper}
|
||||
testID="touchable_feedback_events_button"
|
||||
accessibilityLabel="touchable feedback events"
|
||||
accessibilityTraits="button"
|
||||
accessibilityComponentType="button"
|
||||
onPress={() => this._appendEvent('press')}
|
||||
onPressIn={() => this._appendEvent('pressIn')}
|
||||
onPressOut={() => this._appendEvent('pressOut')}
|
||||
onLongPress={() => this._appendEvent('longPress')}>
|
||||
<Text style={styles.button}>
|
||||
Press Me
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<View testID="touchable_feedback_events_console" style={styles.eventLogBox}>
|
||||
{this.state.eventLog.map((e, ii) => <Text key={ii}>{e}</Text>)}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
_appendEvent: function(eventName) {
|
||||
var limit = 6;
|
||||
var eventLog = this.state.eventLog.slice(0, limit - 1);
|
||||
eventLog.unshift(eventName);
|
||||
this.setState({eventLog});
|
||||
},
|
||||
});
|
||||
|
||||
var TouchableDelayEvents = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
eventLog: [],
|
||||
};
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<View testID="touchable_delay_events">
|
||||
<View style={[styles.row, {justifyContent: 'center'}]}>
|
||||
<TouchableOpacity
|
||||
style={styles.wrapper}
|
||||
testID="touchable_delay_events_button"
|
||||
onPress={() => this._appendEvent('press')}
|
||||
delayPressIn={400}
|
||||
onPressIn={() => this._appendEvent('pressIn - 400ms delay')}
|
||||
delayPressOut={1000}
|
||||
onPressOut={() => this._appendEvent('pressOut - 1000ms delay')}
|
||||
delayLongPress={800}
|
||||
onLongPress={() => this._appendEvent('longPress - 800ms delay')}>
|
||||
<Text style={styles.button}>
|
||||
Press Me
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<View style={styles.eventLogBox} testID="touchable_delay_events_console">
|
||||
{this.state.eventLog.map((e, ii) => <Text key={ii}>{e}</Text>)}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
_appendEvent: function(eventName) {
|
||||
var limit = 6;
|
||||
var eventLog = this.state.eventLog.slice(0, limit - 1);
|
||||
eventLog.unshift(eventName);
|
||||
this.setState({eventLog});
|
||||
},
|
||||
});
|
||||
|
||||
var ForceTouchExample = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
force: 0,
|
||||
};
|
||||
},
|
||||
_renderConsoleText: function() {
|
||||
return View.forceTouchAvailable ?
|
||||
'Force: ' + this.state.force.toFixed(3) :
|
||||
'3D Touch is not available on this device';
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<View testID="touchable_3dtouch_event">
|
||||
<View style={styles.forceTouchBox} testID="touchable_3dtouch_output">
|
||||
<Text>{this._renderConsoleText()}</Text>
|
||||
</View>
|
||||
<View style={[styles.row, {justifyContent: 'center'}]}>
|
||||
<View
|
||||
style={styles.wrapper}
|
||||
testID="touchable_3dtouch_button"
|
||||
onStartShouldSetResponder={() => true}
|
||||
onResponderMove={(event) => this.setState({force: event.nativeEvent.force})}
|
||||
onResponderRelease={(event) => this.setState({force: 0})}>
|
||||
<Text style={styles.button}>
|
||||
Press Me
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
var TouchableHitSlop = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
timesPressed: 0,
|
||||
};
|
||||
},
|
||||
onPress: function() {
|
||||
this.setState({
|
||||
timesPressed: this.state.timesPressed + 1,
|
||||
});
|
||||
},
|
||||
render: function() {
|
||||
var log = '';
|
||||
if (this.state.timesPressed > 1) {
|
||||
log = this.state.timesPressed + 'x onPress';
|
||||
} else if (this.state.timesPressed > 0) {
|
||||
log = 'onPress';
|
||||
}
|
||||
|
||||
return (
|
||||
<View testID="touchable_hit_slop">
|
||||
<View style={[styles.row, {justifyContent: 'center'}]}>
|
||||
<TouchableOpacity
|
||||
onPress={this.onPress}
|
||||
style={styles.hitSlopWrapper}
|
||||
hitSlop={{top: 30, bottom: 30, left: 60, right: 60}}
|
||||
testID="touchable_hit_slop_button">
|
||||
<Text style={styles.hitSlopButton}>
|
||||
Press Outside This View
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<View style={styles.logBox}>
|
||||
<Text>
|
||||
{log}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var TouchableDisabled = React.createClass({
|
||||
render: function() {
|
||||
return (
|
||||
<View>
|
||||
<TouchableOpacity disabled={true} style={[styles.row, styles.block]}>
|
||||
<Text style={styles.disabledButton}>Disabled TouchableOpacity</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity disabled={false} style={[styles.row, styles.block]}>
|
||||
<Text style={styles.button}>Enabled TouchableOpacity</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableHighlight
|
||||
activeOpacity={1}
|
||||
disabled={true}
|
||||
animationVelocity={0}
|
||||
underlayColor="rgb(210, 230, 255)"
|
||||
style={[styles.row, styles.block]}
|
||||
onPress={() => console.log('custom THW text - highlight')}>
|
||||
<Text style={styles.disabledButton}>
|
||||
Disabled TouchableHighlight
|
||||
</Text>
|
||||
</TouchableHighlight>
|
||||
|
||||
<TouchableHighlight
|
||||
activeOpacity={1}
|
||||
animationVelocity={0}
|
||||
underlayColor="rgb(210, 230, 255)"
|
||||
style={[styles.row, styles.block]}
|
||||
onPress={() => console.log('custom THW text - highlight')}>
|
||||
<Text style={styles.button}>
|
||||
Enabled TouchableHighlight
|
||||
</Text>
|
||||
</TouchableHighlight>
|
||||
|
||||
{Platform.OS === 'android' &&
|
||||
<TouchableNativeFeedback
|
||||
style={[styles.row, styles.block]}
|
||||
onPress={() => console.log('custom TNF has been clicked')}
|
||||
background={TouchableNativeFeedback.SelectableBackground()}>
|
||||
<View>
|
||||
<Text style={[styles.button, styles.nativeFeedbackButton]}>
|
||||
Enabled TouchableNativeFeedback
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableNativeFeedback>
|
||||
}
|
||||
|
||||
{Platform.OS === 'android' &&
|
||||
<TouchableNativeFeedback
|
||||
disabled={true}
|
||||
style={[styles.row, styles.block]}
|
||||
onPress={() => console.log('custom TNF has been clicked')}
|
||||
background={TouchableNativeFeedback.SelectableBackground()}>
|
||||
<View>
|
||||
<Text style={[styles.disabledButton, styles.nativeFeedbackButton]}>
|
||||
Disabled TouchableNativeFeedback
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableNativeFeedback>
|
||||
}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var heartImage = {uri: 'https://pbs.twimg.com/media/BlXBfT3CQAA6cVZ.png:small'};
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
row: {
|
||||
justifyContent: 'center',
|
||||
flexDirection: 'row',
|
||||
},
|
||||
icon: {
|
||||
width: 24,
|
||||
height: 24,
|
||||
},
|
||||
image: {
|
||||
width: 50,
|
||||
height: 50,
|
||||
},
|
||||
text: {
|
||||
fontSize: 16,
|
||||
},
|
||||
block: {
|
||||
padding: 10,
|
||||
},
|
||||
button: {
|
||||
color: '#007AFF',
|
||||
},
|
||||
disabledButton: {
|
||||
color: '#007AFF',
|
||||
opacity: 0.5,
|
||||
},
|
||||
nativeFeedbackButton: {
|
||||
textAlign: 'center',
|
||||
margin: 10,
|
||||
},
|
||||
hitSlopButton: {
|
||||
color: 'white',
|
||||
},
|
||||
wrapper: {
|
||||
borderRadius: 8,
|
||||
},
|
||||
wrapperCustom: {
|
||||
borderRadius: 8,
|
||||
padding: 6,
|
||||
},
|
||||
hitSlopWrapper: {
|
||||
backgroundColor: 'red',
|
||||
marginVertical: 30,
|
||||
},
|
||||
logBox: {
|
||||
padding: 20,
|
||||
margin: 10,
|
||||
borderWidth: StyleSheet.hairlineWidth,
|
||||
borderColor: '#f0f0f0',
|
||||
backgroundColor: '#f9f9f9',
|
||||
},
|
||||
eventLogBox: {
|
||||
padding: 10,
|
||||
margin: 10,
|
||||
height: 120,
|
||||
borderWidth: StyleSheet.hairlineWidth,
|
||||
borderColor: '#f0f0f0',
|
||||
backgroundColor: '#f9f9f9',
|
||||
},
|
||||
forceTouchBox: {
|
||||
padding: 10,
|
||||
margin: 10,
|
||||
borderWidth: StyleSheet.hairlineWidth,
|
||||
borderColor: '#f0f0f0',
|
||||
backgroundColor: '#f9f9f9',
|
||||
alignItems: 'center',
|
||||
},
|
||||
textBlock: {
|
||||
fontWeight: '500',
|
||||
color: 'blue',
|
||||
},
|
||||
});
|
||||
|
||||
examples.forEach((example) => {
|
||||
storiesOf('<Touchable*>', module)
|
||||
.add(example.title, () => example.render())
|
||||
})
|
||||
250
examples/View/ViewExample.js
Normal file
250
examples/View/ViewExample.js
Normal file
@@ -0,0 +1,250 @@
|
||||
import React from 'react';
|
||||
import { storiesOf, action } from '@kadira/storybook';
|
||||
import { StyleSheet, Text, TouchableWithoutFeedback, View } from 'react-native'
|
||||
|
||||
/**
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
* Facebook reserves all rights not expressly granted.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
||||
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
box: {
|
||||
backgroundColor: '#527FE4',
|
||||
borderColor: '#000033',
|
||||
borderWidth: 1,
|
||||
},
|
||||
zIndex: {
|
||||
justifyContent: 'space-around',
|
||||
width: 100,
|
||||
height: 50,
|
||||
marginTop: -10,
|
||||
},
|
||||
});
|
||||
|
||||
var ViewBorderStyleExample = React.createClass({
|
||||
getInitialState() {
|
||||
return {
|
||||
showBorder: true
|
||||
};
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<TouchableWithoutFeedback onPress={this._handlePress}>
|
||||
<View>
|
||||
<View style={{
|
||||
borderWidth: 1,
|
||||
borderStyle: this.state.showBorder ? 'dashed' : null,
|
||||
padding: 5
|
||||
}}>
|
||||
<Text style={{fontSize: 11}}>
|
||||
Dashed border style
|
||||
</Text>
|
||||
</View>
|
||||
<View style={{
|
||||
marginTop: 5,
|
||||
borderWidth: 1,
|
||||
borderRadius: 5,
|
||||
borderStyle: this.state.showBorder ? 'dotted' : null,
|
||||
padding: 5
|
||||
}}>
|
||||
<Text style={{fontSize: 11}}>
|
||||
Dotted border style
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
);
|
||||
},
|
||||
|
||||
_handlePress() {
|
||||
this.setState({showBorder: !this.state.showBorder});
|
||||
}
|
||||
});
|
||||
|
||||
var ZIndexExample = React.createClass({
|
||||
getInitialState() {
|
||||
return {
|
||||
flipped: false
|
||||
};
|
||||
},
|
||||
|
||||
render() {
|
||||
const indices = this.state.flipped ? [-1, 0, 1, 2] : [2, 1, 0, -1];
|
||||
return (
|
||||
<TouchableWithoutFeedback onPress={this._handlePress}>
|
||||
<View>
|
||||
<Text style={{paddingBottom: 10}}>Tap to flip sorting order</Text>
|
||||
<View style={[
|
||||
styles.zIndex,
|
||||
{marginTop: 0, backgroundColor: '#E57373', zIndex: indices[0]}
|
||||
]}>
|
||||
<Text>ZIndex {indices[0]}</Text>
|
||||
</View>
|
||||
<View style={[
|
||||
styles.zIndex,
|
||||
{marginLeft: 50, backgroundColor: '#FFF176', zIndex: indices[1]}
|
||||
]}>
|
||||
<Text>ZIndex {indices[1]}</Text>
|
||||
</View>
|
||||
<View style={[
|
||||
styles.zIndex,
|
||||
{marginLeft: 100, backgroundColor: '#81C784', zIndex: indices[2]}
|
||||
]}>
|
||||
<Text>ZIndex {indices[2]}</Text>
|
||||
</View>
|
||||
<View style={[
|
||||
styles.zIndex,
|
||||
{marginLeft: 150, backgroundColor: '#64B5F6', zIndex: indices[3]}
|
||||
]}>
|
||||
<Text>ZIndex {indices[3]}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
);
|
||||
},
|
||||
|
||||
_handlePress() {
|
||||
this.setState({flipped: !this.state.flipped});
|
||||
}
|
||||
});
|
||||
|
||||
const examples = [
|
||||
{
|
||||
title: 'Background Color',
|
||||
render: function() {
|
||||
return (
|
||||
<View style={{backgroundColor: '#527FE4', padding: 5}}>
|
||||
<Text style={{fontSize: 11}}>
|
||||
Blue background
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
}, {
|
||||
title: 'Border',
|
||||
render: function() {
|
||||
return (
|
||||
<View style={{borderColor: '#527FE4', borderWidth: 5, padding: 10}}>
|
||||
<Text style={{fontSize: 11}}>5px blue border</Text>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
}, {
|
||||
title: 'Padding/Margin',
|
||||
render: function() {
|
||||
return (
|
||||
<View style={{borderColor: '#bb0000', borderWidth: 0.5}}>
|
||||
<View style={[styles.box, {padding: 5}]}>
|
||||
<Text style={{fontSize: 11}}>5px padding</Text>
|
||||
</View>
|
||||
<View style={[styles.box, {margin: 5}]}>
|
||||
<Text style={{fontSize: 11}}>5px margin</Text>
|
||||
</View>
|
||||
<View style={[styles.box, {margin: 5, padding: 5, alignSelf: 'flex-start'}]}>
|
||||
<Text style={{fontSize: 11}}>
|
||||
5px margin and padding,
|
||||
</Text>
|
||||
<Text style={{fontSize: 11}}>
|
||||
widthAutonomous=true
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
}, {
|
||||
title: 'Border Radius',
|
||||
render: function() {
|
||||
return (
|
||||
<View style={{borderWidth: 0.5, borderRadius: 5, padding: 5}}>
|
||||
<Text style={{fontSize: 11}}>
|
||||
Too much use of `borderRadius` (especially large radii) on
|
||||
anything which is scrolling may result in dropped frames.
|
||||
Use sparingly.
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
}, {
|
||||
title: 'Border Style',
|
||||
render: function() {
|
||||
return <ViewBorderStyleExample />;
|
||||
},
|
||||
}, {
|
||||
title: 'Circle with Border Radius',
|
||||
render: function() {
|
||||
return (
|
||||
<View style={{borderRadius: 10, borderWidth: 1, width: 20, height: 20}} />
|
||||
);
|
||||
},
|
||||
}, {
|
||||
title: 'Overflow',
|
||||
render: function() {
|
||||
return (
|
||||
<View style={{flexDirection: 'row'}}>
|
||||
<View
|
||||
style={{
|
||||
width: 95,
|
||||
height: 10,
|
||||
marginRight: 10,
|
||||
marginBottom: 5,
|
||||
overflow: 'hidden',
|
||||
borderWidth: 0.5,
|
||||
}}>
|
||||
<View style={{width: 200, height: 20}}>
|
||||
<Text>Overflow hidden</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={{width: 95, height: 10, marginBottom: 5, borderWidth: 0.5}}>
|
||||
<View style={{width: 200, height: 20}}>
|
||||
<Text>Overflow visible</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
}, {
|
||||
title: 'Opacity',
|
||||
render: function() {
|
||||
return (
|
||||
<View>
|
||||
<View style={{opacity: 0}}><Text>Opacity 0</Text></View>
|
||||
<View style={{opacity: 0.1}}><Text>Opacity 0.1</Text></View>
|
||||
<View style={{opacity: 0.3}}><Text>Opacity 0.3</Text></View>
|
||||
<View style={{opacity: 0.5}}><Text>Opacity 0.5</Text></View>
|
||||
<View style={{opacity: 0.7}}><Text>Opacity 0.7</Text></View>
|
||||
<View style={{opacity: 0.9}}><Text>Opacity 0.9</Text></View>
|
||||
<View style={{opacity: 1}}><Text>Opacity 1</Text></View>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
}, {
|
||||
title: 'ZIndex',
|
||||
render: function() {
|
||||
return <ZIndexExample />;
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
examples.forEach((example) => {
|
||||
storiesOf('<View>', module)
|
||||
.add(example.title, () => example.render())
|
||||
})
|
||||
286
examples/View/ViewTransformsExample.js
Normal file
286
examples/View/ViewTransformsExample.js
Normal file
@@ -0,0 +1,286 @@
|
||||
import React from 'react';
|
||||
import { storiesOf, action } from '@kadira/storybook';
|
||||
import { Animated, StyleSheet, Text, View } from 'react-native'
|
||||
|
||||
/**
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
* Facebook reserves all rights not expressly granted.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
||||
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
* @flow
|
||||
*/
|
||||
|
||||
var Flip = React.createClass({
|
||||
getInitialState() {
|
||||
return {
|
||||
theta: new Animated.Value(45),
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
this._animate();
|
||||
},
|
||||
|
||||
_animate() {
|
||||
this.state.theta.setValue(0);
|
||||
Animated.timing(this.state.theta, {
|
||||
toValue: 360,
|
||||
duration: 5000,
|
||||
}).start(this._animate);
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.flipCardContainer}>
|
||||
<Animated.View style={[
|
||||
styles.flipCard,
|
||||
{transform: [
|
||||
{perspective: 850},
|
||||
{rotateX: this.state.theta.interpolate({
|
||||
inputRange: [0, 180],
|
||||
outputRange: ['0deg', '180deg']
|
||||
})},
|
||||
]}]}>
|
||||
<Text style={styles.flipText}>
|
||||
This text is flipping great.
|
||||
</Text>
|
||||
</Animated.View>
|
||||
<Animated.View style={[styles.flipCard, {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
backgroundColor: 'red',
|
||||
transform: [
|
||||
{perspective: 850},
|
||||
{rotateX: this.state.theta.interpolate({
|
||||
inputRange: [0, 180],
|
||||
outputRange: ['180deg', '360deg']
|
||||
})},
|
||||
]}]}>
|
||||
<Text style={styles.flipText}>
|
||||
On the flip side...
|
||||
</Text>
|
||||
</Animated.View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
box1: {
|
||||
left: 0,
|
||||
backgroundColor: 'green',
|
||||
height: 50,
|
||||
top: 0,
|
||||
transform: [
|
||||
{translateX: 100},
|
||||
{translateY: 50},
|
||||
{rotate: '30deg'},
|
||||
{scaleX: 2},
|
||||
{scaleY: 2},
|
||||
],
|
||||
width: 50,
|
||||
},
|
||||
box2: {
|
||||
left: 0,
|
||||
backgroundColor: 'purple',
|
||||
height: 50,
|
||||
top: 0,
|
||||
transform: [
|
||||
{scaleX: 2},
|
||||
{scaleY: 2},
|
||||
{translateX: 100},
|
||||
{translateY: 50},
|
||||
{rotate: '30deg'},
|
||||
],
|
||||
width: 50,
|
||||
},
|
||||
box3step1: {
|
||||
left: 0,
|
||||
backgroundColor: 'lightpink',
|
||||
height: 50,
|
||||
top: 0,
|
||||
transform: [
|
||||
{rotate: '30deg'},
|
||||
],
|
||||
width: 50,
|
||||
},
|
||||
box3step2: {
|
||||
left: 0,
|
||||
backgroundColor: 'hotpink',
|
||||
height: 50,
|
||||
opacity: 0.5,
|
||||
top: 0,
|
||||
transform: [
|
||||
{rotate: '30deg'},
|
||||
{scaleX: 2},
|
||||
{scaleY: 2},
|
||||
],
|
||||
width: 50,
|
||||
},
|
||||
box3step3: {
|
||||
left: 0,
|
||||
backgroundColor: 'deeppink',
|
||||
height: 50,
|
||||
opacity: 0.5,
|
||||
top: 0,
|
||||
transform: [
|
||||
{rotate: '30deg'},
|
||||
{scaleX: 2},
|
||||
{scaleY: 2},
|
||||
{translateX: 10},
|
||||
{translateY: 50},
|
||||
],
|
||||
width: 50,
|
||||
},
|
||||
box4: {
|
||||
left: 0,
|
||||
backgroundColor: 'darkorange',
|
||||
height: 50,
|
||||
top: 0,
|
||||
transform: [
|
||||
{translateX: 20},
|
||||
{translateY: 35},
|
||||
{scale: 2.5},
|
||||
{rotate: '-0.2rad'},
|
||||
],
|
||||
width: 100,
|
||||
},
|
||||
box5: {
|
||||
backgroundColor: 'maroon',
|
||||
height: 50,
|
||||
right: 0,
|
||||
top: 0,
|
||||
width: 50,
|
||||
},
|
||||
box5Transform: {
|
||||
transform: [
|
||||
{translateX: -50},
|
||||
{translateY: 35},
|
||||
{rotate: '50deg'},
|
||||
{scale: 2},
|
||||
],
|
||||
},
|
||||
flipCardContainer: {
|
||||
marginVertical: 40,
|
||||
flex: 1,
|
||||
alignSelf: 'center',
|
||||
},
|
||||
flipCard: {
|
||||
width: 200,
|
||||
height: 200,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: 'blue',
|
||||
backfaceVisibility: 'hidden',
|
||||
},
|
||||
flipText: {
|
||||
width: 90,
|
||||
fontSize: 20,
|
||||
color: 'white',
|
||||
fontWeight: 'bold',
|
||||
}
|
||||
});
|
||||
|
||||
const examples = [
|
||||
{
|
||||
title: 'Perspective',
|
||||
description: 'perspective: 850, rotateX: Animated.timing(0 -> 360)',
|
||||
render(): ReactElement<any> { return <Flip />; }
|
||||
},
|
||||
{
|
||||
title: 'Translate, Rotate, Scale',
|
||||
description: "translateX: 100, translateY: 50, rotate: '30deg', scaleX: 2, scaleY: 2",
|
||||
render() {
|
||||
return (
|
||||
|
||||
<View style={styles.box1} />
|
||||
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Scale, Translate, Rotate, ',
|
||||
description: "scaleX: 2, scaleY: 2, translateX: 100, translateY: 50, rotate: '30deg'",
|
||||
render() {
|
||||
return (
|
||||
|
||||
<View style={styles.box2} />
|
||||
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Rotate',
|
||||
description: "rotate: '30deg'",
|
||||
render() {
|
||||
return (
|
||||
|
||||
<View style={styles.box3step1} />
|
||||
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Rotate, Scale',
|
||||
description: "rotate: '30deg', scaleX: 2, scaleY: 2",
|
||||
render() {
|
||||
return (
|
||||
|
||||
<View style={styles.box3step2} />
|
||||
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Rotate, Scale, Translate ',
|
||||
description: "rotate: '30deg', scaleX: 2, scaleY: 2, translateX: 100, translateY: 50",
|
||||
render() {
|
||||
return (
|
||||
|
||||
<View style={styles.box3step3} />
|
||||
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Translate, Scale, Rotate',
|
||||
description: "translate: [200, 350], scale: 2.5, rotate: '-0.2rad'",
|
||||
render() {
|
||||
return (
|
||||
|
||||
<View style={styles.box4} />
|
||||
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Translate, Rotate, Scale',
|
||||
description: "translate: [-50, 35], rotate: '50deg', scale: 2",
|
||||
render() {
|
||||
return (
|
||||
|
||||
<View style={[styles.box5, styles.box5Transform]} />
|
||||
|
||||
);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
examples.forEach((example) => {
|
||||
storiesOf('<View> transforms', module)
|
||||
.add(example.title, () => example.render())
|
||||
})
|
||||
@@ -1,264 +0,0 @@
|
||||
import GridView from './GridView'
|
||||
import Heading from './Heading'
|
||||
import React from 'react'
|
||||
import { Image, StyleSheet, ScrollView, Text, TextInput, TouchableHighlight, View } from 'react-native'
|
||||
|
||||
export default class App extends React.Component {
|
||||
static propTypes = {
|
||||
style: View.propTypes.style
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
scrollEnabled: true
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const finalRootStyles = [
|
||||
rootStyles.common
|
||||
]
|
||||
|
||||
return (
|
||||
<ScrollView accessibilityRole='main'>
|
||||
<View style={finalRootStyles}>
|
||||
<Heading size='xlarge'>React Native for Web</Heading>
|
||||
<Text>React Native Web takes the core components from <Text
|
||||
accessibilityRole='link' href='https://facebook.github.io/react-native/'>React
|
||||
Native</Text> and brings them to the web. These components provide
|
||||
simple building blocks – touch handling, flexbox layout,
|
||||
scroll views – from which more complex components and apps can be
|
||||
constructed.</Text>
|
||||
|
||||
<Heading size='large'>Image</Heading>
|
||||
<Image
|
||||
accessibilityLabel='accessible image'
|
||||
children={<Text>Inner content</Text>}
|
||||
defaultSource={{
|
||||
uri: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAAGQCAIAAAAP3aGbAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3wkGESkdPWMDggAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAD5UlEQVR42u3UMQ0AAAgEMcC/x7eCCgaSVsIN10kK4IORADAsAMMCDAvAsAAMCzAsAMMCMCzAsAAMC8CwAMMCMCwAwwIMC8CwAAwLMCwAwwIwLMCwAAwLwLAAwwIwLADDAgwLwLAAwwIwLADDAgwLwLAADAswLADDAjAswLAADAvAsADDAjAsAMMCDAvAsAAMCzAsAMMCMCzAsAAMC8CwAMMCMCwAwwIMC8CwAMMCMCwAwwIMC8CwAAwLMCwAwwIwLMCwAAwLwLAAwwIwLADDAgwLwLAADAswLADDAjAswLAADAvAsADDAjAsAMMCDAvAsADDAjAsAMMCDAvAsAAMCzAsAMMCMCzAsAAMC8CwAMMCMCwAwwIMC8CwAAwLMCwAwwIwLMCwAAwLwLAAwwIwLADDAgwLwLAAwwIwLADDAgwLwLAADAswLADDAjAswLAADAvAsADDAjAsAMMCDAvAsAAMCzAsAMMCMCzAsAAMC8CwAMMCMCwAwwIMC8CwAMMCMCwAwwIMC8CwAAwLMCwAwwIwLMCwAAwLwLAAwwIwLADDAgwLwLAADAswLADDAjAswLAADAvAsADDAjAsAMMCDAvAsADDAjAsAMMCDAvAsAAMCzAsAMMCMCzAsAAMC8CwAMMCMCwAwwIMC8CwAAwLMCwAwwIwLMCwAAwLwLAAwwIwLADDAgwLwLAAwwIwLADDAgwLwLAADAswLADDAjAswLAADAvAsADDAjAsAMMCDAvAsAAMCzAsAMMCMCzAsAAMC8CwAMMCMCwAwwIMC8CwAMMCMCwAwwIMC8CwAAwLMCwAwwIwLMCwAAwLwLAAwwIwLADDAgwLwLAADAswLADDAjAswLAADAvAsADDAjAswLAkAAwLwLAAwwIwLADDAgwLwLAADAswLADDAjAswLAADAvAsADDAjAsAMMCDAvAsAAMCzAsAMMCMCzAsAAMC8CwAMMCMCzAsAAMC8CwAMMCMCwAwwIMC8CwAAwLMCwAwwIwLMCwAAwLwLAAwwIwLADDAgwLwLAADAswLADDAjAswLAADAvAsADDAjAswLAADAvAsADDAjAsAMMCDAvAsAAMCzAsAMMCMCzAsAAMC8CwAMMCMCwAwwIMC8CwAAwLMCwAwwIwLMCwAAwLwLAAwwIwLMCwAAwLwLAAwwIwLADDAgwLwLAADAswLADDAjAswLAADAvAsADDAjAsAMMCDAvAsAAMCzAsAMMCMCzAsAAMC8CwAMMCMCzAsAAMC8CwAMMCMCwAwwIMC8CwAAwLMCwAwwIwLMCwAAwLwLAAwwIwLADDAgwLwLAADAswLADDAjAswLAALi04UQW9HF910gAAAABJRU5ErkJggg=='
|
||||
}}
|
||||
onError={(e) => { console.log('Image.onError', e) }}
|
||||
onLoad={(e) => { console.log('Image.onLoad', e) }}
|
||||
onLoadEnd={() => { console.log('Image.onLoadEnd') }}
|
||||
onLoadStart={() => { console.log('Image.onLoadStart') }}
|
||||
resizeMode={'contain'}
|
||||
source={{
|
||||
height: 400,
|
||||
uri: 'http://facebook.github.io/react/img/logo_og.png',
|
||||
width: 400
|
||||
}}
|
||||
style={{
|
||||
borderWidth: '5px'
|
||||
}}
|
||||
testID='Example.image'
|
||||
/>
|
||||
|
||||
<Heading size='large'>Text</Heading>
|
||||
<Text
|
||||
onPress={(e) => { console.log('Text.onPress', e) }}
|
||||
testID={'Example.text'}
|
||||
>
|
||||
PRESS ME.
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent vel
|
||||
lectus urna. Aliquam vitae justo porttitor, aliquam erat nec,
|
||||
venenatis diam. Vivamus facilisis augue non urna mattis ultricies.
|
||||
Suspendisse et vulputate enim, a maximus nulla. Vivamus imperdiet
|
||||
hendrerit consequat. Aliquam lorem quam, elementum eget ex nec,
|
||||
ultrices porttitor nibh. Nulla pellentesque urna leo, a aliquet elit
|
||||
rhoncus a. Aenean ultricies, nunc a interdum dictum, dui odio
|
||||
scelerisque mauris, a fringilla elit ligula vel sem. Sed vel aliquet
|
||||
ipsum, sed rhoncus velit. Vivamus commodo pretium libero id placerat.
|
||||
</Text>
|
||||
<Text numberOfLines={1}>
|
||||
TRUNCATED after 1 line.
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent vel
|
||||
lectus urna. Aliquam vitae justo porttitor, aliquam erat nec,
|
||||
venenatis diam. Vivamus facilisis augue non urna mattis ultricies.
|
||||
Suspendisse et vulputate enim, a maximus nulla. Vivamus imperdiet
|
||||
hendrerit consequat.
|
||||
</Text>
|
||||
|
||||
<Heading size='large'>TextInput</Heading>
|
||||
<TextInput
|
||||
keyboardType='default'
|
||||
onBlur={(e) => { console.log('TextInput.onBlur', e) }}
|
||||
onChange={(e) => { console.log('TextInput.onChange', e) }}
|
||||
onChangeText={(e) => { console.log('TextInput.onChangeText', e) }}
|
||||
onFocus={(e) => { console.log('TextInput.onFocus', e) }}
|
||||
onSelectionChange={(e) => { console.log('TextInput.onSelectionChange', e) }}
|
||||
/>
|
||||
<TextInput secureTextEntry />
|
||||
<TextInput defaultValue='read only' editable={false} />
|
||||
<TextInput
|
||||
style={{ flex:1, height: 60, padding: 20, fontSize: 20, textAlign: 'center' }}
|
||||
keyboardType='email-address' placeholder='you@domain.com' placeholderTextColor='red'
|
||||
/>
|
||||
<TextInput keyboardType='numeric' />
|
||||
<TextInput keyboardType='phone-pad' />
|
||||
<TextInput defaultValue='https://delete-me' keyboardType='url' placeholder='https://www.some-website.com' selectTextOnFocus />
|
||||
<TextInput
|
||||
defaultValue='default value'
|
||||
maxNumberOfLines={10}
|
||||
multiline
|
||||
numberOfLines={5}
|
||||
/>
|
||||
|
||||
<Heading size='large'>Touchable</Heading>
|
||||
<TouchableHighlight
|
||||
accessibilityLabel={'Touchable element'}
|
||||
activeHighlight='lightblue'
|
||||
activeOpacity={0.8}
|
||||
onLongPress={(e) => { console.log('Touchable.onLongPress', e) }}
|
||||
onPress={(e) => { console.log('Touchable.onPress', e) }}
|
||||
onPressIn={(e) => { console.log('Touchable.onPressIn', e) }}
|
||||
onPressOut={(e) => { console.log('Touchable.onPressOut', e) }}
|
||||
>
|
||||
<View style={styles.touchableArea}>
|
||||
<Text>Touchable area (press, long press)</Text>
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
|
||||
<Heading size='large'>View</Heading>
|
||||
<Heading>Default layout</Heading>
|
||||
<View>
|
||||
{[ 1, 2, 3, 4, 5, 6 ].map((item, i) => {
|
||||
return (
|
||||
<View key={i} style={styles.box}>
|
||||
<Text>{item}</Text>
|
||||
</View>
|
||||
)
|
||||
})}
|
||||
</View>
|
||||
|
||||
<Heading>Row layout</Heading>
|
||||
<View style={styles.row}>
|
||||
{[ 1, 2, 3, 4, 5, 6 ].map((item, i) => {
|
||||
return (
|
||||
<View key={i} style={styles.box}>
|
||||
<Text>{item}</Text>
|
||||
</View>
|
||||
)
|
||||
})}
|
||||
</View>
|
||||
|
||||
<Heading>pointerEvents</Heading>
|
||||
<GridView alley='10px'>
|
||||
{['box-none', 'box-only', 'none'].map((value, i) => {
|
||||
return (
|
||||
<View
|
||||
accessibilityRole='link'
|
||||
children={value}
|
||||
href='https://google.com'
|
||||
key={i}
|
||||
pointerEvents={value}
|
||||
style={styles.pointerEventsBox}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</GridView>
|
||||
|
||||
<Heading size='large'>ScrollView</Heading>
|
||||
<label>
|
||||
<input
|
||||
checked={this.state.scrollEnabled}
|
||||
onChange={() => this.setState({
|
||||
scrollEnabled: !this.state.scrollEnabled
|
||||
})}
|
||||
type='checkbox'
|
||||
/> Enable scroll
|
||||
</label>
|
||||
|
||||
<Heading>Default layout</Heading>
|
||||
<View style={styles.scrollViewContainer}>
|
||||
<ScrollView
|
||||
contentContainerStyle={styles.scrollViewContentContainerStyle}
|
||||
onScroll={e => console.log('ScrollView.onScroll', e)}
|
||||
scrollEnabled={this.state.scrollEnabled}
|
||||
scrollEventThrottle={1} // 1 event per second
|
||||
style={styles.scrollViewStyle}
|
||||
>
|
||||
{Array.from({ length: 50 }).map((item, i) => (
|
||||
<View key={i} style={styles.box}>
|
||||
<Text>{i}</Text>
|
||||
</View>
|
||||
))}
|
||||
</ScrollView>
|
||||
</View>
|
||||
|
||||
<Heading>Horizontal layout</Heading>
|
||||
<View style={styles.scrollViewContainer}>
|
||||
<ScrollView
|
||||
contentContainerStyle={styles.scrollViewContentContainerStyle}
|
||||
horizontal
|
||||
onScroll={e => console.log('ScrollView.onScroll', e)}
|
||||
scrollEnabled={this.state.scrollEnabled}
|
||||
scrollEventThrottle={1} // 1 event per second
|
||||
style={styles.scrollViewStyle}
|
||||
>
|
||||
{Array.from({ length: 50 }).map((item, i) => (
|
||||
<View key={i} style={[ styles.box, styles.horizontalBox ]}>
|
||||
<Text>{i}</Text>
|
||||
</View>
|
||||
))}
|
||||
</ScrollView>
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const rootStyles = StyleSheet.create({
|
||||
common: {
|
||||
marginVertical: 0,
|
||||
marginHorizontal: 'auto'
|
||||
},
|
||||
mqSmall: {
|
||||
maxWidth: '400px'
|
||||
},
|
||||
mqLarge: {
|
||||
maxWidth: '600px'
|
||||
}
|
||||
})
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
row: {
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap'
|
||||
},
|
||||
box: {
|
||||
alignItems: 'center',
|
||||
flexGrow: 1,
|
||||
justifyContent: 'center',
|
||||
borderWidth: 1
|
||||
},
|
||||
horizontalBox: {
|
||||
width: '50px'
|
||||
},
|
||||
boxFull: {
|
||||
width: '100%'
|
||||
},
|
||||
pointerEventsBox: {
|
||||
alignItems: 'center',
|
||||
borderWidth: '1px',
|
||||
flexGrow: 1,
|
||||
height: '100px',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
touchableArea: {
|
||||
alignItems: 'center',
|
||||
borderWidth: 1,
|
||||
height: '200px',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
scrollViewContainer: {
|
||||
height: '200px'
|
||||
},
|
||||
scrollViewStyle: {
|
||||
borderWidth: '1px'
|
||||
},
|
||||
scrollViewContentContainerStyle: {
|
||||
padding: '10px'
|
||||
}
|
||||
})
|
||||
@@ -1,66 +0,0 @@
|
||||
import React, { Component, PropTypes } from 'react'
|
||||
import { StyleSheet, View } from 'react-native'
|
||||
|
||||
export default class GridView extends Component {
|
||||
static propTypes = {
|
||||
alley: PropTypes.string,
|
||||
children: PropTypes.oneOfType([
|
||||
PropTypes.element,
|
||||
PropTypes.arrayOf(PropTypes.element)
|
||||
]),
|
||||
gutter: PropTypes.string,
|
||||
style: PropTypes.object
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
alley: '0px',
|
||||
gutter: '0px'
|
||||
}
|
||||
|
||||
render() {
|
||||
const { alley, children, gutter, style, ...other } = this.props
|
||||
|
||||
const rootStyle = {
|
||||
...style,
|
||||
...styles.root
|
||||
}
|
||||
|
||||
const contentContainerStyle = {
|
||||
...styles.contentContainer,
|
||||
marginHorizontal: `calc(-0.5 * ${alley})`,
|
||||
paddingHorizontal: `${gutter}`
|
||||
}
|
||||
|
||||
const newChildren = React.Children.map(children, (child) => {
|
||||
return child && React.cloneElement(child, {
|
||||
style: {
|
||||
...child.props.style,
|
||||
...styles.column,
|
||||
marginHorizontal: `calc(0.5 * ${alley})`
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return (
|
||||
<View className='GridView' {...other} style={rootStyle}>
|
||||
<View style={contentContainerStyle}>
|
||||
{newChildren}
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
overflow: 'hidden'
|
||||
},
|
||||
contentContainer: {
|
||||
flexDirection: 'row',
|
||||
flexGrow: 1
|
||||
},
|
||||
// distribute all space (rather than extra space)
|
||||
column: {
|
||||
flexBasis: '0%'
|
||||
}
|
||||
})
|
||||
@@ -1,35 +0,0 @@
|
||||
import React from 'react'
|
||||
import { StyleSheet, Text } from 'react-native'
|
||||
|
||||
const Heading = ({ children, size = 'normal' }) => (
|
||||
<Text
|
||||
accessibilityRole='heading'
|
||||
children={children}
|
||||
style={{ ...styles.root, ...sizeStyles[size] }}
|
||||
/>
|
||||
)
|
||||
|
||||
const sizeStyles = StyleSheet.create({
|
||||
xlarge: {
|
||||
fontSize: '2rem',
|
||||
marginBottom: '1em'
|
||||
},
|
||||
large: {
|
||||
fontSize: '1.5rem',
|
||||
marginBottom: '1em',
|
||||
marginTop: '1em'
|
||||
},
|
||||
normal: {
|
||||
fontSize: '1.25rem',
|
||||
marginBottom: '0.5em',
|
||||
marginTop: '0.5em'
|
||||
}
|
||||
})
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
fontFamily: '"Helvetica Neue", arial, sans-serif'
|
||||
}
|
||||
})
|
||||
|
||||
export default Heading
|
||||
@@ -1,6 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<meta charset="utf-8">
|
||||
<title>React Native for Web</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<div id="react-root"></div>
|
||||
<script src="/bundle.js"></script>
|
||||
@@ -1,10 +0,0 @@
|
||||
import { AppRegistry } from 'react-native'
|
||||
import App from './components/App'
|
||||
import Game2048 from './2048/Game2048'
|
||||
import TicTacToeApp from './TicTacToe/TicTacToe'
|
||||
|
||||
AppRegistry.registerComponent('App', () => App)
|
||||
|
||||
AppRegistry.runApplication('App', {
|
||||
rootTag: document.getElementById('react-root')
|
||||
})
|
||||
@@ -1,6 +1,6 @@
|
||||
const webpack = require('webpack')
|
||||
|
||||
const testEntry = 'tests.webpack.js'
|
||||
const testEntry = 'src/tests.webpack.js'
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
|
||||
31
package.json
31
package.json
@@ -1,17 +1,19 @@
|
||||
{
|
||||
"name": "react-native-web",
|
||||
"version": "0.0.27",
|
||||
"version": "0.0.39",
|
||||
"description": "React Native for Web",
|
||||
"main": "dist/index.js",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "del ./dist && mkdir dist && babel src -d dist --ignore **/__tests__,src/modules/specHelpers",
|
||||
"build": "del ./dist && mkdir dist && babel src -d dist --ignore **/__tests__",
|
||||
"build:storybook": "build-storybook -o storybook -c ./examples/.storybook",
|
||||
"deploy:storybook": "git checkout gh-pages && git add -A && git commit -m \"Storybook deploy\" && git push origin gh-pages && git checkout -",
|
||||
"build:umd": "webpack --config webpack.config.js --sort-assets-by --progress",
|
||||
"examples": "webpack-dev-server --config examples/webpack.config.js --inline --hot --colors --quiet",
|
||||
"lint": "eslint src",
|
||||
"prepublish": "npm run build && npm run build:umd",
|
||||
"storybook": "start-storybook -p 9001 -c ./examples/.storybook",
|
||||
"test": "karma start karma.config.js",
|
||||
"test:watch": "npm run test -- --no-single-run"
|
||||
},
|
||||
@@ -19,21 +21,19 @@
|
||||
"animated": "^0.1.3",
|
||||
"babel-runtime": "^6.9.2",
|
||||
"fbjs": "^0.8.1",
|
||||
"inline-style-prefix-all": "^2.0.2",
|
||||
"inline-style-prefixer": "^2.0.0",
|
||||
"lodash": "^4.13.1",
|
||||
"react-dom": "^15.1.0",
|
||||
"react-textarea-autosize": "^4.0.2",
|
||||
"react-timer-mixin": "^0.13.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@kadira/storybook": "^1.38.0",
|
||||
"babel-cli": "^6.10.1",
|
||||
"babel-core": "^6.9.1",
|
||||
"babel-eslint": "^6.0.4",
|
||||
"babel-core": "^6.10.4",
|
||||
"babel-eslint": "^6.1.0",
|
||||
"babel-loader": "^6.2.4",
|
||||
"babel-plugin-transform-decorators-legacy": "^1.3.4",
|
||||
"babel-preset-es2015": "^6.9.0",
|
||||
"babel-preset-react": "^6.5.0",
|
||||
"babel-preset-stage-1": "^6.5.0",
|
||||
"babel-preset-react-native": "^1.9.0",
|
||||
"del-cli": "^0.2.0",
|
||||
"enzyme": "^2.3.0",
|
||||
"eslint": "^2.12.0",
|
||||
@@ -42,20 +42,21 @@
|
||||
"eslint-plugin-promise": "^1.3.2",
|
||||
"eslint-plugin-react": "^5.1.1",
|
||||
"eslint-plugin-standard": "^1.3.2",
|
||||
"file-loader": "^0.9.0",
|
||||
"karma": "^0.13.22",
|
||||
"karma-browserstack-launcher": "^1.0.1",
|
||||
"karma-chrome-launcher": "^1.0.1",
|
||||
"karma-firefox-launcher": "^1.0.0",
|
||||
"karma-mocha": "^1.0.1",
|
||||
"karma-mocha": "^1.1.1",
|
||||
"karma-mocha-reporter": "^2.0.4",
|
||||
"karma-sourcemap-loader": "^0.3.7",
|
||||
"karma-webpack": "^1.7.0",
|
||||
"mocha": "^2.5.3",
|
||||
"node-libs-browser": "^0.5.3",
|
||||
"react": "^15.1.0",
|
||||
"react-addons-test-utils": "^15.1.0",
|
||||
"webpack": "^1.13.1",
|
||||
"webpack-dev-server": "^1.14.1"
|
||||
"react": "^15.2.0",
|
||||
"react-addons-test-utils": "^15.2.0",
|
||||
"url-loader": "^0.5.7",
|
||||
"webpack": "^1.13.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^15.1.0"
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import Portal from '../../components/Portal'
|
||||
import React, { Component, PropTypes } from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import StyleSheet from '../StyleSheet'
|
||||
import View from '../../components/View'
|
||||
|
||||
@@ -16,25 +14,15 @@ class ReactNativeApp extends Component {
|
||||
|
||||
return (
|
||||
<View style={styles.appContainer}>
|
||||
<RootComponent {...initialProps} ref={this._createRootRef} rootTag={rootTag} />
|
||||
<Portal onModalVisibilityChanged={this._handleModalVisibilityChange} />
|
||||
<RootComponent {...initialProps} rootTag={rootTag} />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
_createRootRef = (component) => {
|
||||
this._root = component
|
||||
}
|
||||
|
||||
_handleModalVisibilityChange = (modalVisible) => {
|
||||
ReactDOM.findDOMNode(this._root).setAttribute('aria-hidden', `${modalVisible}`)
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
/**
|
||||
* Ensure that the application covers the whole screen. This prevents the
|
||||
* Portal content from being clipped.
|
||||
* Ensure that the application covers the whole screen.
|
||||
*/
|
||||
appContainer: {
|
||||
position: 'absolute',
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
import assert from 'assert'
|
||||
import React from 'react'
|
||||
import { elementId } from '../../StyleSheet'
|
||||
import { prerenderApplication } from '../renderApplication'
|
||||
import React from 'react'
|
||||
|
||||
const component = () => <div />
|
||||
|
||||
suite('apis/AppRegistry/renderApplication', () => {
|
||||
test('prerenderApplication', () => {
|
||||
const { html, style, styleElement } = prerenderApplication(component, {})
|
||||
const { html, styleElement } = prerenderApplication(component, {})
|
||||
|
||||
assert.ok(html.indexOf('<div ') > -1)
|
||||
assert.ok(typeof style === 'string')
|
||||
assert.equal(styleElement.type, 'style')
|
||||
assert.equal(styleElement.props.id, elementId)
|
||||
assert.equal(styleElement.props.dangerouslySetInnerHTML.__html, style)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -13,17 +13,9 @@ import ReactDOMServer from 'react-dom/server'
|
||||
import ReactNativeApp from './ReactNativeApp'
|
||||
import StyleSheet from '../../apis/StyleSheet'
|
||||
|
||||
const renderStyleSheetToString = () => StyleSheet.renderToString()
|
||||
const styleAsElement = (style) => <style dangerouslySetInnerHTML={{ __html: style }} id={StyleSheet.elementId} />
|
||||
const styleAsTagString = (style) => `<style id="${StyleSheet.elementId}">${style}</style>`
|
||||
|
||||
export default function renderApplication(RootComponent: Component, initialProps: Object, rootTag: any) {
|
||||
invariant(rootTag, 'Expect to have a valid rootTag, instead got ', rootTag)
|
||||
|
||||
// insert style sheet if needed
|
||||
const styleElement = document.getElementById(StyleSheet.elementId)
|
||||
if (!styleElement) { rootTag.insertAdjacentHTML('beforebegin', styleAsTagString(renderStyleSheetToString())) }
|
||||
|
||||
const component = (
|
||||
<ReactNativeApp
|
||||
initialProps={initialProps}
|
||||
@@ -42,7 +34,6 @@ export function prerenderApplication(RootComponent: Component, initialProps: Obj
|
||||
/>
|
||||
)
|
||||
const html = ReactDOMServer.renderToString(component)
|
||||
const style = renderStyleSheetToString()
|
||||
const styleElement = styleAsElement(style)
|
||||
return { html, style, styleElement }
|
||||
const styleElement = StyleSheet.render()
|
||||
return { html, styleElement }
|
||||
}
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var TouchHistoryMath = {
|
||||
/**
|
||||
* This code is optimized and not intended to look beautiful. This allows
|
||||
* computing of touch centroids that have moved after `touchesChangedAfter`
|
||||
* timeStamp. You can compute the current centroid involving all touches
|
||||
* moves after `touchesChangedAfter`, or you can compute the previous
|
||||
* centroid of all touches that were moved after `touchesChangedAfter`.
|
||||
*
|
||||
* @param {TouchHistoryMath} touchHistory Standard Responder touch track
|
||||
* data.
|
||||
* @param {number} touchesChangedAfter timeStamp after which moved touches
|
||||
* are considered "actively moving" - not just "active".
|
||||
* @param {boolean} isXAxis Consider `x` dimension vs. `y` dimension.
|
||||
* @param {boolean} ofCurrent Compute current centroid for actively moving
|
||||
* touches vs. previous centroid of now actively moving touches.
|
||||
* @return {number} value of centroid in specified dimension.
|
||||
*/
|
||||
centroidDimension: function(touchHistory, touchesChangedAfter, isXAxis, ofCurrent) {
|
||||
var touchBank = touchHistory.touchBank;
|
||||
var total = 0;
|
||||
var count = 0;
|
||||
|
||||
var oneTouchData = touchHistory.numberActiveTouches === 1 ?
|
||||
touchHistory.touchBank[touchHistory.indexOfSingleActiveTouch] : null;
|
||||
|
||||
if (oneTouchData !== null) {
|
||||
if (oneTouchData.touchActive && oneTouchData.currentTimeStamp > touchesChangedAfter) {
|
||||
total += ofCurrent && isXAxis ? oneTouchData.currentPageX :
|
||||
ofCurrent && !isXAxis ? oneTouchData.currentPageY :
|
||||
!ofCurrent && isXAxis ? oneTouchData.previousPageX :
|
||||
oneTouchData.previousPageY;
|
||||
count = 1;
|
||||
}
|
||||
} else {
|
||||
for (var i = 0; i < touchBank.length; i++) {
|
||||
var touchTrack = touchBank[i];
|
||||
if (touchTrack !== null &&
|
||||
touchTrack !== undefined &&
|
||||
touchTrack.touchActive &&
|
||||
touchTrack.currentTimeStamp >= touchesChangedAfter) {
|
||||
var toAdd; // Yuck, program temporarily in invalid state.
|
||||
if (ofCurrent && isXAxis) {
|
||||
toAdd = touchTrack.currentPageX;
|
||||
} else if (ofCurrent && !isXAxis) {
|
||||
toAdd = touchTrack.currentPageY;
|
||||
} else if (!ofCurrent && isXAxis) {
|
||||
toAdd = touchTrack.previousPageX;
|
||||
} else {
|
||||
toAdd = touchTrack.previousPageY;
|
||||
}
|
||||
total += toAdd;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return count > 0 ? total / count : TouchHistoryMath.noCentroid;
|
||||
},
|
||||
|
||||
currentCentroidXOfTouchesChangedAfter: function(touchHistory, touchesChangedAfter) {
|
||||
return TouchHistoryMath.centroidDimension(
|
||||
touchHistory,
|
||||
touchesChangedAfter,
|
||||
true, // isXAxis
|
||||
true // ofCurrent
|
||||
);
|
||||
},
|
||||
|
||||
currentCentroidYOfTouchesChangedAfter: function(touchHistory, touchesChangedAfter) {
|
||||
return TouchHistoryMath.centroidDimension(
|
||||
touchHistory,
|
||||
touchesChangedAfter,
|
||||
false, // isXAxis
|
||||
true // ofCurrent
|
||||
);
|
||||
},
|
||||
|
||||
previousCentroidXOfTouchesChangedAfter: function(touchHistory, touchesChangedAfter) {
|
||||
return TouchHistoryMath.centroidDimension(
|
||||
touchHistory,
|
||||
touchesChangedAfter,
|
||||
true, // isXAxis
|
||||
false // ofCurrent
|
||||
);
|
||||
},
|
||||
|
||||
previousCentroidYOfTouchesChangedAfter: function(touchHistory, touchesChangedAfter) {
|
||||
return TouchHistoryMath.centroidDimension(
|
||||
touchHistory,
|
||||
touchesChangedAfter,
|
||||
false, // isXAxis
|
||||
false // ofCurrent
|
||||
);
|
||||
},
|
||||
|
||||
currentCentroidX: function(touchHistory) {
|
||||
return TouchHistoryMath.centroidDimension(
|
||||
touchHistory,
|
||||
0, // touchesChangedAfter
|
||||
true, // isXAxis
|
||||
true // ofCurrent
|
||||
);
|
||||
},
|
||||
|
||||
currentCentroidY: function(touchHistory) {
|
||||
return TouchHistoryMath.centroidDimension(
|
||||
touchHistory,
|
||||
0, // touchesChangedAfter
|
||||
false, // isXAxis
|
||||
true // ofCurrent
|
||||
);
|
||||
},
|
||||
|
||||
noCentroid: -1,
|
||||
};
|
||||
|
||||
module.exports = TouchHistoryMath;
|
||||
@@ -6,8 +6,7 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
import normalizeNativeEvent from './normalizeNativeEvent';
|
||||
var TouchHistoryMath = require('./TouchHistoryMath');
|
||||
var TouchHistoryMath = require('react/lib/TouchHistoryMath');
|
||||
|
||||
var currentCentroidXOfTouchesChangedAfter =
|
||||
TouchHistoryMath.currentCentroidXOfTouchesChangedAfter;
|
||||
@@ -288,11 +287,11 @@ var PanResponder = {
|
||||
var panHandlers = {
|
||||
onStartShouldSetResponder: function(e) {
|
||||
return config.onStartShouldSetPanResponder === undefined ? false :
|
||||
config.onStartShouldSetPanResponder(normalizeEvent(e), gestureState);
|
||||
config.onStartShouldSetPanResponder(e, gestureState);
|
||||
},
|
||||
onMoveShouldSetResponder: function(e) {
|
||||
return config.onMoveShouldSetPanResponder === undefined ? false :
|
||||
config.onMoveShouldSetPanResponder(normalizeEvent(e), gestureState);
|
||||
config.onMoveShouldSetPanResponder(e, gestureState);
|
||||
},
|
||||
onStartShouldSetResponderCapture: function(e) {
|
||||
// TODO: Actually, we should reinitialize the state any time
|
||||
@@ -302,12 +301,12 @@ var PanResponder = {
|
||||
PanResponder._initializeGestureState(gestureState);
|
||||
}
|
||||
}
|
||||
else if (e.nativeEvent.type === 'mousedown') {
|
||||
else if (e.nativeEvent.originalEvent && e.nativeEvent.originalEvent.type === 'mousedown') {
|
||||
PanResponder._initializeGestureState(gestureState);
|
||||
}
|
||||
gestureState.numberActiveTouches = e.touchHistory.numberActiveTouches;
|
||||
return config.onStartShouldSetPanResponderCapture !== undefined ?
|
||||
config.onStartShouldSetPanResponderCapture(normalizeEvent(e), gestureState) : false;
|
||||
config.onStartShouldSetPanResponderCapture(e, gestureState) : false;
|
||||
},
|
||||
|
||||
onMoveShouldSetResponderCapture: function(e) {
|
||||
@@ -320,7 +319,7 @@ var PanResponder = {
|
||||
}
|
||||
PanResponder._updateGestureStateOnMove(gestureState, touchHistory);
|
||||
return config.onMoveShouldSetPanResponderCapture ?
|
||||
config.onMoveShouldSetPanResponderCapture(normalizeEvent(e), gestureState) : false;
|
||||
config.onMoveShouldSetPanResponderCapture(e, gestureState) : false;
|
||||
},
|
||||
|
||||
onResponderGrant: function(e) {
|
||||
@@ -328,25 +327,25 @@ var PanResponder = {
|
||||
gestureState.y0 = currentCentroidY(e.touchHistory);
|
||||
gestureState.dx = 0;
|
||||
gestureState.dy = 0;
|
||||
config.onPanResponderGrant && config.onPanResponderGrant(normalizeEvent(e), gestureState);
|
||||
config.onPanResponderGrant && config.onPanResponderGrant(e, gestureState);
|
||||
// TODO: t7467124 investigate if this can be removed
|
||||
return config.onShouldBlockNativeResponder === undefined ? true :
|
||||
config.onShouldBlockNativeResponder();
|
||||
},
|
||||
|
||||
onResponderReject: function(e) {
|
||||
config.onPanResponderReject && config.onPanResponderReject(normalizeEvent(e), gestureState);
|
||||
config.onPanResponderReject && config.onPanResponderReject(e, gestureState);
|
||||
},
|
||||
|
||||
onResponderRelease: function(e) {
|
||||
config.onPanResponderRelease && config.onPanResponderRelease(normalizeEvent(e), gestureState);
|
||||
config.onPanResponderRelease && config.onPanResponderRelease(e, gestureState);
|
||||
PanResponder._initializeGestureState(gestureState);
|
||||
},
|
||||
|
||||
onResponderStart: function(e) {
|
||||
var touchHistory = e.touchHistory;
|
||||
gestureState.numberActiveTouches = touchHistory.numberActiveTouches;
|
||||
config.onPanResponderStart && config.onPanResponderStart(normalizeEvent(e), gestureState);
|
||||
config.onPanResponderStart && config.onPanResponderStart(e, gestureState);
|
||||
},
|
||||
|
||||
onResponderMove: function(e) {
|
||||
@@ -359,13 +358,13 @@ var PanResponder = {
|
||||
// Filter out any touch moves past the first one - we would have
|
||||
// already processed multi-touch geometry during the first event.
|
||||
PanResponder._updateGestureStateOnMove(gestureState, touchHistory);
|
||||
config.onPanResponderMove && config.onPanResponderMove(normalizeEvent(e), gestureState);
|
||||
config.onPanResponderMove && config.onPanResponderMove(e, gestureState);
|
||||
},
|
||||
|
||||
onResponderEnd: function(e) {
|
||||
var touchHistory = e.touchHistory;
|
||||
gestureState.numberActiveTouches = touchHistory.numberActiveTouches;
|
||||
config.onPanResponderEnd && config.onPanResponderEnd(normalizeEvent(e), gestureState);
|
||||
config.onPanResponderEnd && config.onPanResponderEnd(e, gestureState);
|
||||
},
|
||||
|
||||
onResponderTerminate: function(e) {
|
||||
@@ -376,17 +375,11 @@ var PanResponder = {
|
||||
|
||||
onResponderTerminationRequest: function(e) {
|
||||
return config.onPanResponderTerminationRequest === undefined ? true :
|
||||
config.onPanResponderTerminationRequest(normalizeEvent(e), gestureState);
|
||||
config.onPanResponderTerminationRequest(e, gestureState);
|
||||
},
|
||||
};
|
||||
return {panHandlers: panHandlers};
|
||||
},
|
||||
};
|
||||
|
||||
function normalizeEvent(e) {
|
||||
const normalizedEvent = Object.create(e);
|
||||
normalizedEvent.nativeEvent = normalizeNativeEvent(e.nativeEvent, e.type);
|
||||
return normalizedEvent;
|
||||
}
|
||||
|
||||
module.exports = PanResponder;
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2016-present, Nicolas Gallagher.
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import prefixAll from 'inline-style-prefix-all'
|
||||
import hyphenate from './hyphenate'
|
||||
import expandStyle from './expandStyle'
|
||||
import flattenStyle from './flattenStyle'
|
||||
import processTransform from './processTransform'
|
||||
import { predefinedClassNames } from './predefs'
|
||||
|
||||
let stylesCache = {}
|
||||
let uniqueID = 0
|
||||
|
||||
const getCacheKey = (prop, value) => `${prop}:${value}`
|
||||
|
||||
const normalizeStyle = (style) => {
|
||||
return processTransform(expandStyle(flattenStyle(style)))
|
||||
}
|
||||
|
||||
const createCssDeclarations = (style) => {
|
||||
return Object.keys(style).map((prop) => {
|
||||
const property = hyphenate(prop)
|
||||
const value = style[prop]
|
||||
if (Array.isArray(value)) {
|
||||
return value.reduce((acc, curr) => {
|
||||
acc += `${property}:${curr};`
|
||||
return acc
|
||||
}, '')
|
||||
} else {
|
||||
return `${property}:${value};`
|
||||
}
|
||||
}).sort().join('')
|
||||
}
|
||||
|
||||
class StyleSheetRegistry {
|
||||
/* for testing */
|
||||
static _reset() {
|
||||
stylesCache = {}
|
||||
uniqueID = 0
|
||||
}
|
||||
|
||||
static renderToString() {
|
||||
let str = `/* ${uniqueID} unique declarations */`
|
||||
|
||||
return Object.keys(stylesCache).reduce((str, key) => {
|
||||
const id = stylesCache[key].id
|
||||
const style = stylesCache[key].style
|
||||
const declarations = createCssDeclarations(style)
|
||||
const rule = `\n.${id}{${declarations}}`
|
||||
str += rule
|
||||
return str
|
||||
}, str)
|
||||
}
|
||||
|
||||
static registerStyle(style: Object): number {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
Object.freeze(style)
|
||||
}
|
||||
|
||||
const normalizedStyle = normalizeStyle(style)
|
||||
|
||||
Object.keys(normalizedStyle).forEach((prop) => {
|
||||
const value = normalizedStyle[prop]
|
||||
const cacheKey = getCacheKey(prop, value)
|
||||
const exists = stylesCache[cacheKey] && stylesCache[cacheKey].id
|
||||
if (!exists) {
|
||||
const id = ++uniqueID
|
||||
// add new declaration to the store
|
||||
stylesCache[cacheKey] = {
|
||||
id: `__style${id}`,
|
||||
style: prefixAll({ [prop]: value })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return style
|
||||
}
|
||||
|
||||
static getStyleAsNativeProps(styleSheetObject, canUseCSS = false) {
|
||||
const classList = []
|
||||
const normalizedStyle = normalizeStyle(styleSheetObject)
|
||||
let style = {}
|
||||
|
||||
for (const prop in normalizedStyle) {
|
||||
const value = normalizedStyle[prop]
|
||||
const cacheKey = getCacheKey(prop, value)
|
||||
let selector = stylesCache[cacheKey] && stylesCache[cacheKey].id || predefinedClassNames[cacheKey]
|
||||
|
||||
if (selector && canUseCSS) {
|
||||
classList.push(selector)
|
||||
} else {
|
||||
style[prop] = normalizedStyle[prop]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* React 15 removed undocumented support for fallback values in
|
||||
* inline-styles. For now, pick the last value and regress browser support
|
||||
* for CSS features like flexbox.
|
||||
*/
|
||||
const finalStyle = Object.keys(prefixAll(style)).reduce((acc, prop) => {
|
||||
const value = style[prop]
|
||||
acc[prop] = Array.isArray(value) ? value[value.length - 1] : value
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
return {
|
||||
className: classList.join(' '),
|
||||
style: finalStyle
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = StyleSheetRegistry
|
||||
@@ -10,7 +10,7 @@ import { PropTypes } from 'react'
|
||||
import ImageStylePropTypes from '../../components/Image/ImageStylePropTypes'
|
||||
import TextStylePropTypes from '../../components/Text/TextStylePropTypes'
|
||||
import ViewStylePropTypes from '../../components/View/ViewStylePropTypes'
|
||||
import invariant from 'fbjs/lib/invariant'
|
||||
import warning from 'fbjs/lib/warning'
|
||||
|
||||
class StyleSheetValidation {
|
||||
static validateStyleProp(prop, style, caller) {
|
||||
@@ -19,10 +19,11 @@ class StyleSheetValidation {
|
||||
const message1 = `"${prop}" is not a valid style property.`
|
||||
const message2 = '\nValid style props: ' + JSON.stringify(Object.keys(allStylePropTypes).sort(), null, ' ')
|
||||
styleError(message1, style, caller, message2)
|
||||
}
|
||||
const error = allStylePropTypes[prop](style, prop, caller, 'prop')
|
||||
if (error) {
|
||||
styleError(error.message, style, caller)
|
||||
} else {
|
||||
const error = allStylePropTypes[prop](style, prop, caller, 'prop')
|
||||
if (error) {
|
||||
styleError(error.message, style, caller)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,7 +44,7 @@ class StyleSheetValidation {
|
||||
}
|
||||
|
||||
const styleError = (message1, style, caller, message2) => {
|
||||
invariant(
|
||||
warning(
|
||||
false,
|
||||
message1 + '\n' + (caller || '<<unknown>>') + ': ' +
|
||||
JSON.stringify(style, null, ' ') + (message2 || '')
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import { PropTypes } from 'react'
|
||||
|
||||
const ArrayOfNumberPropType = PropTypes.arrayOf(PropTypes.number)
|
||||
const numberOrString = PropTypes.oneOfType([ PropTypes.number, PropTypes.string ])
|
||||
|
||||
const TransformMatrixPropType = function (
|
||||
props : Object,
|
||||
propName : string,
|
||||
componentName : string
|
||||
) : ?Error {
|
||||
if (props.transform && props.transformMatrix) {
|
||||
return new Error(
|
||||
'transformMatrix and transform styles cannot be used on the same ' +
|
||||
'component'
|
||||
)
|
||||
}
|
||||
return ArrayOfNumberPropType(props, propName, componentName)
|
||||
}
|
||||
|
||||
const TransformPropTypes = {
|
||||
transform: PropTypes.arrayOf(
|
||||
PropTypes.oneOfType([
|
||||
PropTypes.shape({ perspective: numberOrString }),
|
||||
PropTypes.shape({ rotate: numberOrString }),
|
||||
PropTypes.shape({ rotateX: numberOrString }),
|
||||
PropTypes.shape({ rotateY: numberOrString }),
|
||||
PropTypes.shape({ rotateZ: numberOrString }),
|
||||
PropTypes.shape({ scale: numberOrString }),
|
||||
PropTypes.shape({ scaleX: numberOrString }),
|
||||
PropTypes.shape({ scaleY: numberOrString }),
|
||||
PropTypes.shape({ skewX: numberOrString }),
|
||||
PropTypes.shape({ skewY: numberOrString }),
|
||||
PropTypes.shape({ translateX: numberOrString }),
|
||||
PropTypes.shape({ translateY: numberOrString }),
|
||||
PropTypes.shape({ translateZ: numberOrString }),
|
||||
PropTypes.shape({ translate3d: PropTypes.string })
|
||||
])
|
||||
),
|
||||
transformMatrix: TransformMatrixPropType
|
||||
}
|
||||
|
||||
module.exports = TransformPropTypes
|
||||
@@ -1,55 +0,0 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
import assert from 'assert'
|
||||
import StyleSheetRegistry from '../StyleSheetRegistry'
|
||||
|
||||
suite('apis/StyleSheet/StyleSheetRegistry', () => {
|
||||
setup(() => {
|
||||
StyleSheetRegistry._reset()
|
||||
})
|
||||
|
||||
test('static renderToString', () => {
|
||||
const style1 = { alignItems: 'center', opacity: 1 }
|
||||
const style2 = { alignItems: 'center', opacity: 1 }
|
||||
StyleSheetRegistry.registerStyle(style1)
|
||||
StyleSheetRegistry.registerStyle(style2)
|
||||
|
||||
const actual = StyleSheetRegistry.renderToString()
|
||||
const expected = `/* 2 unique declarations */
|
||||
.__style1{-ms-flex-align:center;-webkit-align-items:center;-webkit-box-align:center;align-items:center;}
|
||||
.__style2{opacity:1;}`
|
||||
|
||||
assert.equal(actual, expected)
|
||||
})
|
||||
|
||||
test('static getStyleAsNativeProps', () => {
|
||||
const style = { borderColorTop: 'white', opacity: 1 }
|
||||
const style1 = { opacity: 1 }
|
||||
StyleSheetRegistry.registerStyle(style1)
|
||||
|
||||
// canUseCSS = false
|
||||
const actual1 = StyleSheetRegistry.getStyleAsNativeProps(style)
|
||||
const expected1 = {
|
||||
className: '',
|
||||
style: { borderColorTop: 'white', opacity: 1 }
|
||||
}
|
||||
assert.deepEqual(actual1, expected1)
|
||||
|
||||
// canUseCSS = true
|
||||
const actual2 = StyleSheetRegistry.getStyleAsNativeProps(style, true)
|
||||
const expected2 = {
|
||||
className: '__style1',
|
||||
style: { borderColorTop: 'white' }
|
||||
}
|
||||
assert.deepEqual(actual2, expected2)
|
||||
})
|
||||
})
|
||||
13
src/apis/StyleSheet/__tests__/createReactStyleObject-test.js
Normal file
13
src/apis/StyleSheet/__tests__/createReactStyleObject-test.js
Normal file
@@ -0,0 +1,13 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
import assert from 'assert'
|
||||
import createReactStyleObject from '../createReactStyleObject'
|
||||
|
||||
suite('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 }
|
||||
|
||||
assert.deepEqual(createReactStyleObject(reactNativeStyle), expectedStyle)
|
||||
})
|
||||
})
|
||||
@@ -4,20 +4,29 @@ import assert from 'assert'
|
||||
import expandStyle from '../expandStyle'
|
||||
|
||||
suite('apis/StyleSheet/expandStyle', () => {
|
||||
test('style resolution', () => {
|
||||
test('shortform -> longform', () => {
|
||||
const initial = {
|
||||
borderTopWidth: 1,
|
||||
borderWidth: 2,
|
||||
borderStyle: 'solid',
|
||||
boxSizing: 'border-box',
|
||||
borderBottomColor: 'white',
|
||||
borderBottomWidth: 1,
|
||||
borderWidth: 0,
|
||||
marginTop: 50,
|
||||
marginVertical: 25,
|
||||
margin: 10
|
||||
}
|
||||
|
||||
const expected = {
|
||||
borderTopWidth: '1px',
|
||||
borderLeftWidth: '2px',
|
||||
borderRightWidth: '2px',
|
||||
borderBottomWidth: '2px',
|
||||
borderBottomStyle: 'solid',
|
||||
borderLeftStyle: 'solid',
|
||||
borderRightStyle: 'solid',
|
||||
boxSizing: 'border-box',
|
||||
borderBottomColor: 'white',
|
||||
borderTopStyle: 'solid',
|
||||
borderTopWidth: '0px',
|
||||
borderLeftWidth: '0px',
|
||||
borderRightWidth: '0px',
|
||||
borderBottomWidth: '1px',
|
||||
marginTop: '50px',
|
||||
marginBottom: '25px',
|
||||
marginLeft: '10px',
|
||||
@@ -27,6 +36,18 @@ suite('apis/StyleSheet/expandStyle', () => {
|
||||
assert.deepEqual(expandStyle(initial), expected)
|
||||
})
|
||||
|
||||
test('textAlignVertical', () => {
|
||||
const initial = {
|
||||
textAlignVertical: 'center'
|
||||
}
|
||||
|
||||
const expected = {
|
||||
verticalAlign: 'middle'
|
||||
}
|
||||
|
||||
assert.deepEqual(expandStyle(initial), expected)
|
||||
})
|
||||
|
||||
test('flex', () => {
|
||||
const value = 10
|
||||
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
import assert from 'assert'
|
||||
import hyphenate from '../hyphenate'
|
||||
|
||||
suite('apis/StyleSheet/hyphenate', () => {
|
||||
test('style property', () => {
|
||||
assert.equal(hyphenate('alignItems'), 'align-items')
|
||||
assert.equal(hyphenate('color'), 'color')
|
||||
})
|
||||
test('vendor prefixed style property', () => {
|
||||
assert.equal(hyphenate('MozTransition'), '-moz-transition')
|
||||
assert.equal(hyphenate('msTransition'), '-ms-transition')
|
||||
assert.equal(hyphenate('WebkitTransition'), '-webkit-transition')
|
||||
})
|
||||
})
|
||||
@@ -1,60 +1,70 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
import { resetCSS, predefinedCSS } from '../predefs'
|
||||
import assert from 'assert'
|
||||
import { defaultStyles } from '../predefs'
|
||||
import isPlainObject from 'lodash/isPlainObject'
|
||||
import StyleSheet from '..'
|
||||
|
||||
const styles = { root: { opacity: 1 } }
|
||||
|
||||
suite('apis/StyleSheet', () => {
|
||||
setup(() => {
|
||||
StyleSheet._destroy()
|
||||
StyleSheet._reset()
|
||||
})
|
||||
|
||||
test('absoluteFill', () => {
|
||||
assert(Number.isInteger(StyleSheet.absoluteFill) === true)
|
||||
})
|
||||
|
||||
test('absoluteFillObject', () => {
|
||||
assert.ok(isPlainObject(StyleSheet.absoluteFillObject) === true)
|
||||
})
|
||||
|
||||
suite('create', () => {
|
||||
test('returns styles object', () => {
|
||||
assert.equal(StyleSheet.create(styles), styles)
|
||||
test('replaces styles with numbers', () => {
|
||||
const style = StyleSheet.create({ root: { opacity: 1 } })
|
||||
assert(Number.isInteger(style.root) === true)
|
||||
})
|
||||
|
||||
test('updates already-rendered style sheet', () => {
|
||||
// setup
|
||||
const div = document.createElement('div')
|
||||
document.body.appendChild(div)
|
||||
StyleSheet.create(styles)
|
||||
div.innerHTML = `<style id='${StyleSheet.elementId}'>${StyleSheet.renderToString()}</style>`
|
||||
|
||||
// test
|
||||
test('renders a style sheet in the browser', () => {
|
||||
StyleSheet.create({ root: { color: 'red' } })
|
||||
assert.equal(
|
||||
document.getElementById(StyleSheet.elementId).textContent,
|
||||
`${resetCSS}\n${predefinedCSS}\n` +
|
||||
`/* 2 unique declarations */\n` +
|
||||
`.__style1{opacity:1;}\n` +
|
||||
'.__style2{color:red;}'
|
||||
document.getElementById('__react-native-style').textContent,
|
||||
defaultStyles
|
||||
)
|
||||
|
||||
// teardown
|
||||
document.body.removeChild(div)
|
||||
})
|
||||
})
|
||||
|
||||
test('renderToString', () => {
|
||||
StyleSheet.create(styles)
|
||||
test('flatten', () => {
|
||||
assert(typeof StyleSheet.flatten === 'function')
|
||||
})
|
||||
|
||||
test('hairlineWidth', () => {
|
||||
assert(Number.isInteger(StyleSheet.hairlineWidth) === true)
|
||||
})
|
||||
|
||||
test('render', () => {
|
||||
assert.equal(
|
||||
StyleSheet.renderToString(),
|
||||
`${resetCSS}\n${predefinedCSS}\n` +
|
||||
`/* 1 unique declarations */\n` +
|
||||
'.__style1{opacity:1;}'
|
||||
StyleSheet.render().props.dangerouslySetInnerHTML.__html,
|
||||
defaultStyles
|
||||
)
|
||||
})
|
||||
|
||||
test('resolve', () => {
|
||||
assert.deepEqual(
|
||||
StyleSheet.resolve({ className: 'test', style: styles.root }),
|
||||
{
|
||||
StyleSheet.resolve({
|
||||
className: 'test',
|
||||
style: { opacity: 1 }
|
||||
style: {
|
||||
display: 'flex',
|
||||
opacity: 1,
|
||||
pointerEvents: 'box-none'
|
||||
}
|
||||
}),
|
||||
{
|
||||
className: 'test __style_df __style_pebn',
|
||||
style: {
|
||||
display: 'flex',
|
||||
opacity: 1,
|
||||
pointerEvents: 'box-none'
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
@@ -9,5 +9,6 @@ suite('apis/StyleSheet/normalizeValue', () => {
|
||||
})
|
||||
test('ignores unitless property values', () => {
|
||||
assert.deepEqual(normalizeValue('flexGrow', 1), 1)
|
||||
assert.deepEqual(normalizeValue('scale', 2), 2)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -8,13 +8,14 @@ suite('apis/StyleSheet/processTransform', () => {
|
||||
const style = {
|
||||
transform: [
|
||||
{ scaleX: 20 },
|
||||
{ translateX: 20 },
|
||||
{ rotate: '20deg' }
|
||||
]
|
||||
}
|
||||
|
||||
assert.deepEqual(
|
||||
processTransform(style),
|
||||
{ transform: 'scaleX(20) rotate(20deg)' }
|
||||
{ transform: 'scaleX(20) translateX(20px) rotate(20deg)' }
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
22
src/apis/StyleSheet/createReactStyleObject.js
Normal file
22
src/apis/StyleSheet/createReactStyleObject.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import expandStyle from './expandStyle'
|
||||
import flattenStyle from '../../modules/flattenStyle'
|
||||
import prefixAll from 'inline-style-prefixer/static'
|
||||
import processTransform from './processTransform'
|
||||
|
||||
const addVendorPrefixes = (style) => {
|
||||
let prefixedStyles = prefixAll(style)
|
||||
// React@15 removed undocumented support for fallback values in
|
||||
// inline-styles. Revert array values to the standard CSS value
|
||||
for (const prop in prefixedStyles) {
|
||||
const value = prefixedStyles[prop]
|
||||
if (Array.isArray(value)) {
|
||||
prefixedStyles[prop] = value[value.length - 1]
|
||||
}
|
||||
}
|
||||
return prefixedStyles
|
||||
}
|
||||
|
||||
const _createReactDOMStyleObject = (reactNativeStyle) => processTransform(expandStyle(flattenStyle(reactNativeStyle)))
|
||||
const createReactDOMStyleObject = (reactNativeStyle) => addVendorPrefixes(_createReactDOMStyleObject(reactNativeStyle))
|
||||
|
||||
module.exports = createReactDOMStyleObject
|
||||
@@ -1,6 +1,18 @@
|
||||
/**
|
||||
* The browser implements the CSS cascade, where the order of properties is a
|
||||
* factor in determining which styles to paint. React Native is different in
|
||||
* giving precedence to the more specific styles. For example, the value of
|
||||
* `paddingTop` takes precedence over that of `padding`.
|
||||
*
|
||||
* This module creates mutally exclusive style declarations by expanding all of
|
||||
* React Native's supported shortform properties (e.g. `padding`) to their
|
||||
* longfrom equivalents.
|
||||
*/
|
||||
|
||||
import normalizeValue from './normalizeValue'
|
||||
|
||||
const styleShortHands = {
|
||||
const emptyObject = {}
|
||||
const styleShortFormProperties = {
|
||||
borderColor: [ 'borderTopColor', 'borderRightColor', 'borderBottomColor', 'borderLeftColor' ],
|
||||
borderRadius: [ 'borderTopLeftRadius', 'borderTopRightRadius', 'borderBottomRightRadius', 'borderBottomLeftRadius' ],
|
||||
borderStyle: [ 'borderTopStyle', 'borderRightStyle', 'borderBottomStyle', 'borderLeftStyle' ],
|
||||
@@ -16,50 +28,46 @@ const styleShortHands = {
|
||||
writingDirection: [ 'direction' ]
|
||||
}
|
||||
|
||||
/**
|
||||
* Alpha-sort properties, apart from shorthands – they must appear before the
|
||||
* longhand properties that they expand into. This lets more specific styles
|
||||
* override less specific styles, whatever the order in which they were
|
||||
* originally declared.
|
||||
*/
|
||||
const sortProps = (propsArray) => propsArray.sort((a, b) => {
|
||||
const expandedA = styleShortHands[a]
|
||||
const expandedB = styleShortHands[b]
|
||||
if (expandedA && expandedA.indexOf(b) > -1) {
|
||||
return -1
|
||||
} else if (expandedB && expandedB.indexOf(a) > -1) {
|
||||
return 1
|
||||
}
|
||||
return a < b ? -1 : a > b ? 1 : 0
|
||||
const alphaSort = (arr) => arr.sort((a, b) => {
|
||||
if (a < b) { return -1 }
|
||||
if (a > b) { return 1 }
|
||||
return 0
|
||||
})
|
||||
|
||||
/**
|
||||
* Expand the shorthand properties to isolate every declaration from the others.
|
||||
*/
|
||||
const expandStyle = (style) => {
|
||||
const propsArray = Object.keys(style)
|
||||
const sortedProps = sortProps(propsArray)
|
||||
const createStyleReducer = (originalStyle) => {
|
||||
const originalStyleProps = Object.keys(originalStyle)
|
||||
|
||||
return sortedProps.reduce((resolvedStyle, key) => {
|
||||
const expandedProps = styleShortHands[key]
|
||||
const value = normalizeValue(key, style[key])
|
||||
return (style, prop) => {
|
||||
const value = normalizeValue(prop, originalStyle[prop])
|
||||
const longFormProperties = styleShortFormProperties[prop]
|
||||
|
||||
// React Native treats `flex:1` like `flex:1 1 auto`
|
||||
if (key === 'flex') {
|
||||
resolvedStyle.flexGrow = value
|
||||
resolvedStyle.flexShrink = 1
|
||||
resolvedStyle.flexBasis = 'auto'
|
||||
} else if (key === 'textAlignVertical') {
|
||||
resolvedStyle.verticalAlign = (value === 'center' ? 'middle' : value)
|
||||
} else if (expandedProps) {
|
||||
expandedProps.forEach((prop, i) => {
|
||||
resolvedStyle[expandedProps[i]] = value
|
||||
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
|
||||
}
|
||||
})
|
||||
} else {
|
||||
resolvedStyle[key] = value
|
||||
style[prop] = value
|
||||
}
|
||||
return resolvedStyle
|
||||
}, {})
|
||||
return style
|
||||
}
|
||||
}
|
||||
|
||||
const expandStyle = (style = emptyObject) => {
|
||||
const sortedStyleProps = alphaSort(Object.keys(style))
|
||||
const styleReducer = createStyleReducer(style)
|
||||
return sortedStyleProps.reduce(styleReducer, {})
|
||||
}
|
||||
|
||||
module.exports = expandStyle
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2016-present, Nicolas Gallagher.
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
import invariant from 'fbjs/lib/invariant'
|
||||
|
||||
module.exports = function flattenStyle(style): ?Object {
|
||||
if (!style) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
invariant(style !== true, 'style may be false but not true')
|
||||
|
||||
if (!Array.isArray(style)) {
|
||||
return style
|
||||
}
|
||||
|
||||
const result = {}
|
||||
for (let i = 0; i < style.length; ++i) {
|
||||
const computedStyle = flattenStyle(style[i])
|
||||
if (computedStyle) {
|
||||
for (const key in computedStyle) {
|
||||
result[key] = computedStyle[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
module.exports = (string) => (string.replace(/([A-Z])/g, '-$1').toLowerCase()).replace(/^ms-/, '-ms-')
|
||||
@@ -1,75 +1,76 @@
|
||||
import { resetCSS, predefinedCSS } from './predefs'
|
||||
import flattenStyle from './flattenStyle'
|
||||
import StyleSheetRegistry from './StyleSheetRegistry'
|
||||
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 { defaultStyles, mapStyleToClassName } from './predefs'
|
||||
|
||||
const ELEMENT_ID = 'react-stylesheet'
|
||||
let isRendered = false
|
||||
let lastStyleSheet = ''
|
||||
let styleElement
|
||||
const STYLE_SHEET_ID = '__react-native-style'
|
||||
|
||||
/**
|
||||
* Initialize the store with pointer-event styles mapping to our custom pointer
|
||||
* event classes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Destroy existing styles
|
||||
*/
|
||||
const _destroy = () => {
|
||||
isRendered = false
|
||||
StyleSheetRegistry._reset()
|
||||
const _injectStyleSheet = () => {
|
||||
// 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', renderToString()) }
|
||||
isRendered = true
|
||||
}
|
||||
|
||||
const _reset = () => {
|
||||
if (styleElement) { document.head.removeChild(styleElement) }
|
||||
styleElement = null
|
||||
isRendered = false
|
||||
}
|
||||
|
||||
const absoluteFillObject = { position: 'absolute', left: 0, right: 0, top: 0, bottom: 0 }
|
||||
const absoluteFill = ReactNativePropRegistry.register(absoluteFillObject)
|
||||
|
||||
const create = (styles: Object): Object => {
|
||||
for (const key in styles) {
|
||||
StyleSheetValidation.validateStyle(key, styles)
|
||||
StyleSheetRegistry.registerStyle(styles[key])
|
||||
if (!isRendered && ExecutionEnvironment.canUseDOM) {
|
||||
_injectStyleSheet()
|
||||
}
|
||||
|
||||
// update the style sheet in place
|
||||
if (isRendered) {
|
||||
const stylesheet = document.getElementById(ELEMENT_ID)
|
||||
if (stylesheet) {
|
||||
const newStyleSheet = renderToString()
|
||||
if (lastStyleSheet !== newStyleSheet) {
|
||||
stylesheet.textContent = newStyleSheet
|
||||
lastStyleSheet = newStyleSheet
|
||||
}
|
||||
} else if (process.env.NODE_ENV !== 'production') {
|
||||
console.error(`ReactNative: cannot find "${ELEMENT_ID}" element`)
|
||||
const result = {}
|
||||
for (let key in styles) {
|
||||
StyleSheetValidation.validateStyle(key, styles)
|
||||
result[key] = ReactNativePropRegistry.register(styles[key])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const render = () => <style dangerouslySetInnerHTML={{ __html: defaultStyles }} id={STYLE_SHEET_ID} />
|
||||
|
||||
const renderToString = () => `<style id="${STYLE_SHEET_ID}">${defaultStyles}</style>`
|
||||
|
||||
/**
|
||||
* Accepts React props and converts style declarations to classNames when necessary
|
||||
*/
|
||||
const resolve = (props) => {
|
||||
let className = props.className || ''
|
||||
let style = createReactStyleObject(props.style)
|
||||
for (const prop in style) {
|
||||
const value = style[prop]
|
||||
const replacementClassName = mapStyleToClassName(prop, value)
|
||||
if (replacementClassName) {
|
||||
className += ` ${replacementClassName}`
|
||||
// delete style[prop]
|
||||
}
|
||||
}
|
||||
|
||||
return styles
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the styles as a CSS style sheet
|
||||
*/
|
||||
const renderToString = () => {
|
||||
const css = StyleSheetRegistry.renderToString()
|
||||
isRendered = true
|
||||
return `${resetCSS}\n${predefinedCSS}\n${css}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Accepts React props and converts inline styles to single purpose classes
|
||||
* where possible.
|
||||
*/
|
||||
const resolve = ({ className, style = {} }) => {
|
||||
const props = StyleSheetRegistry.getStyleAsNativeProps(style, isRendered)
|
||||
return {
|
||||
...props,
|
||||
className: className ? `${props.className} ${className}`.trim() : props.className
|
||||
}
|
||||
return { className, style }
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
_destroy,
|
||||
_reset,
|
||||
absoluteFill,
|
||||
absoluteFillObject,
|
||||
create,
|
||||
elementId: ELEMENT_ID,
|
||||
hairlineWidth: 1,
|
||||
flatten: flattenStyle,
|
||||
renderToString,
|
||||
/* @platform web */
|
||||
render,
|
||||
/* @platform web */
|
||||
resolve
|
||||
}
|
||||
|
||||
@@ -19,7 +19,12 @@ const unitlessNumbers = {
|
||||
fillOpacity: true,
|
||||
strokeDashoffset: true,
|
||||
strokeOpacity: true,
|
||||
strokeWidth: true
|
||||
strokeWidth: true,
|
||||
// transform types
|
||||
scale: true,
|
||||
scaleX: true,
|
||||
scaleY: true,
|
||||
scaleZ: true
|
||||
}
|
||||
|
||||
const normalizeValue = (property, value) => {
|
||||
|
||||
@@ -1,24 +1,38 @@
|
||||
/**
|
||||
* Reset unwanted styles beyond the control of React inline styles
|
||||
*/
|
||||
export const resetCSS =
|
||||
`/* React Native for Web */
|
||||
html {font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}
|
||||
body {margin:0}
|
||||
button::-moz-focus-inner, input::-moz-focus-inner {border:0;padding:0}
|
||||
input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration {display:none}`
|
||||
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'
|
||||
|
||||
/**
|
||||
* Custom pointer event styles
|
||||
*/
|
||||
export const predefinedCSS =
|
||||
`/* pointer-events */
|
||||
.__style_pea, .__style_pebo, .__style_pebn * {pointer-events:auto}
|
||||
.__style_pen, .__style_pebo *, .__style_pebn {pointer-events:none}`
|
||||
|
||||
export const predefinedClassNames = {
|
||||
'pointerEvents:auto': '__style_pea',
|
||||
'pointerEvents:box-none': '__style_pebn',
|
||||
'pointerEvents:box-only': '__style_pebo',
|
||||
'pointerEvents:none': '__style_pen'
|
||||
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 mapStyleToClassName = (prop, value) => {
|
||||
return styleAsClassName[prop] && styleAsClassName[prop][value]
|
||||
}
|
||||
|
||||
// reset unwanted styles beyond the control of React inline styles
|
||||
const resetCSS =
|
||||
'/* React Native */\n' +
|
||||
'html {font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}\n' +
|
||||
'body {margin:0}\n' +
|
||||
'button::-moz-focus-inner, input::-moz-focus-inner {border:0;padding:0}\n' +
|
||||
'input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration {display:none}'
|
||||
|
||||
const helperCSS =
|
||||
// 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}`
|
||||
|
||||
export const defaultStyles = `${resetCSS}\n${helperCSS}`
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import normalizeValue from './normalizeValue'
|
||||
|
||||
// { scale: 2 } => 'scale(2)'
|
||||
// { translateX: 20 } => 'translateX(20px)'
|
||||
const mapTransform = (transform) => {
|
||||
var key = Object.keys(transform)[0]
|
||||
return `${key}(${transform[key]})`
|
||||
const type = Object.keys(transform)[0]
|
||||
const value = normalizeValue(type, transform[type])
|
||||
return `${type}(${value})`
|
||||
}
|
||||
|
||||
// [1,2,3,4,5,6] => 'matrix3d(1,2,3,4,5,6)'
|
||||
|
||||
@@ -109,11 +109,11 @@ suite('apis/UIManager', () => {
|
||||
assert.equal(node.getAttribute('class'), 'existing extra')
|
||||
})
|
||||
|
||||
test('adds new style to existing style', () => {
|
||||
test('adds correct DOM styles to existing style', () => {
|
||||
const node = createNode({ color: 'red' })
|
||||
const props = { style: { opacity: 0 } }
|
||||
const props = { style: { marginVertical: 0, opacity: 0 } }
|
||||
UIManager.updateView(node, props, componentStub)
|
||||
assert.equal(node.getAttribute('style'), 'color: red; opacity: 0;')
|
||||
assert.equal(node.getAttribute('style'), 'color: red; margin-top: 0px; margin-bottom: 0px; opacity: 0;')
|
||||
})
|
||||
|
||||
test('replaces input and textarea text', () => {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import createReactStyleObject from '../StyleSheet/createReactStyleObject'
|
||||
import CSSPropertyOperations from 'react/lib/CSSPropertyOperations'
|
||||
import flattenStyle from '../StyleSheet/flattenStyle'
|
||||
import processTransform from '../StyleSheet/processTransform'
|
||||
|
||||
const _measureLayout = (node, relativeToNativeNode, callback) => {
|
||||
const relativeNode = relativeToNativeNode || node.parentNode
|
||||
@@ -34,9 +33,8 @@ const UIManager = {
|
||||
_measureLayout(node, relativeTo, onSuccess)
|
||||
},
|
||||
|
||||
updateView(node, props, component /* only needed to surpress React errors in __DEV__ */) {
|
||||
updateView(node, props, component /* only needed to surpress React errors in development */) {
|
||||
for (const prop in props) {
|
||||
let nativeProp
|
||||
const value = props[prop]
|
||||
|
||||
switch (prop) {
|
||||
@@ -44,17 +42,18 @@ const UIManager = {
|
||||
// convert styles to DOM-styles
|
||||
CSSPropertyOperations.setValueForStyles(
|
||||
node,
|
||||
processTransform(flattenStyle(value)),
|
||||
createReactStyleObject(value),
|
||||
component._reactInternalInstance
|
||||
)
|
||||
break
|
||||
case 'class':
|
||||
case 'className':
|
||||
nativeProp = '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)
|
||||
break
|
||||
}
|
||||
case 'text':
|
||||
case 'value':
|
||||
// native platforms use `text` prop to replace text input value
|
||||
|
||||
20
src/apis/Vibration/index.js
Normal file
20
src/apis/Vibration/index.js
Normal file
@@ -0,0 +1,20 @@
|
||||
const vibrate = (pattern) => {
|
||||
if ('vibrate' in window.navigator) {
|
||||
if (typeof pattern === 'number' || Array.isArray(pattern)) {
|
||||
window.navigator.vibrate(pattern)
|
||||
} else {
|
||||
throw new Error('Vibration pattern should be a number or array')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const Vibration = {
|
||||
cancel() {
|
||||
vibrate(0)
|
||||
},
|
||||
vibrate(pattern) {
|
||||
vibrate(pattern)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Vibration
|
||||
@@ -1,4 +1,4 @@
|
||||
import NativeMethodsDecorator from '../../modules/NativeMethodsDecorator'
|
||||
import applyNativeMethods from '../../modules/applyNativeMethods'
|
||||
import React, { Component, PropTypes } from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import StyleSheet from '../../apis/StyleSheet'
|
||||
@@ -19,7 +19,6 @@ const keyframeEffects = [
|
||||
{ transform: 'scale(0.95)', opacity: 0.5 }
|
||||
]
|
||||
|
||||
@NativeMethodsDecorator
|
||||
class ActivityIndicator extends Component {
|
||||
static propTypes = {
|
||||
animating: PropTypes.bool,
|
||||
@@ -87,6 +86,8 @@ class ActivityIndicator extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
applyNativeMethods(ActivityIndicator)
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
alignItems: 'center',
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import keyMirror from 'fbjs/lib/keyMirror'
|
||||
|
||||
const ImageResizeMode = keyMirror({
|
||||
center: null,
|
||||
contain: null,
|
||||
cover: null,
|
||||
none: null,
|
||||
repeat: null,
|
||||
stretch: null
|
||||
})
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { PropTypes } from 'react'
|
||||
import ColorPropType from '../../apis/StyleSheet/ColorPropType'
|
||||
import LayoutPropTypes from '../../apis/StyleSheet/LayoutPropTypes'
|
||||
import TransformPropTypes from '../../apis/StyleSheet/TransformPropTypes'
|
||||
import BorderPropTypes from '../../propTypes/BorderPropTypes'
|
||||
import ColorPropType from '../../propTypes/ColorPropType'
|
||||
import LayoutPropTypes from '../../propTypes/LayoutPropTypes'
|
||||
import TransformPropTypes from '../../propTypes/TransformPropTypes'
|
||||
import ImageResizeMode from './ImageResizeMode'
|
||||
|
||||
const hiddenOrVisible = PropTypes.oneOf([ 'hidden', 'visible' ])
|
||||
|
||||
module.exports = {
|
||||
...BorderPropTypes,
|
||||
...LayoutPropTypes,
|
||||
...TransformPropTypes,
|
||||
backfaceVisibility: hiddenOrVisible,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
/* global window */
|
||||
import createNativeComponent from '../../modules/createNativeComponent'
|
||||
import applyNativeMethods from '../../modules/applyNativeMethods'
|
||||
import createReactDOMComponent from '../../modules/createReactDOMComponent'
|
||||
import ImageResizeMode from './ImageResizeMode'
|
||||
import ImageStylePropTypes from './ImageStylePropTypes'
|
||||
import NativeMethodsDecorator from '../../modules/NativeMethodsDecorator'
|
||||
import resolveAssetSource from './resolveAssetSource'
|
||||
import React, { Component, PropTypes } from 'react'
|
||||
import StyleSheet from '../../apis/StyleSheet'
|
||||
import StyleSheetPropType from '../../apis/StyleSheet/StyleSheetPropType'
|
||||
import StyleSheetPropType from '../../propTypes/StyleSheetPropType'
|
||||
import View from '../View'
|
||||
|
||||
const STATUS_ERRORED = 'ERRORED'
|
||||
@@ -22,21 +22,23 @@ const ImageSourcePropType = PropTypes.oneOfType([
|
||||
PropTypes.string
|
||||
])
|
||||
|
||||
@NativeMethodsDecorator
|
||||
class Image extends Component {
|
||||
static displayName = 'Image'
|
||||
|
||||
static propTypes = {
|
||||
accessibilityLabel: createNativeComponent.propTypes.accessibilityLabel,
|
||||
accessible: createNativeComponent.propTypes.accessible,
|
||||
accessibilityLabel: createReactDOMComponent.propTypes.accessibilityLabel,
|
||||
accessible: createReactDOMComponent.propTypes.accessible,
|
||||
children: PropTypes.any,
|
||||
defaultSource: ImageSourcePropType,
|
||||
onError: PropTypes.func,
|
||||
onLayout: PropTypes.func,
|
||||
onLoad: PropTypes.func,
|
||||
onLoadEnd: PropTypes.func,
|
||||
onLoadStart: PropTypes.func,
|
||||
resizeMode: PropTypes.oneOf(['contain', 'cover', 'none', 'stretch']),
|
||||
resizeMode: PropTypes.oneOf(['center', 'contain', 'cover', 'none', 'repeat', 'stretch']),
|
||||
source: ImageSourcePropType,
|
||||
style: StyleSheetPropType(ImageStylePropTypes),
|
||||
testID: createNativeComponent.propTypes.testID
|
||||
testID: createReactDOMComponent.propTypes.testID
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
@@ -83,6 +85,7 @@ class Image extends Component {
|
||||
accessible,
|
||||
children,
|
||||
defaultSource,
|
||||
onLayout,
|
||||
source,
|
||||
testID
|
||||
} = this.props
|
||||
@@ -108,6 +111,7 @@ class Image extends Component {
|
||||
accessibilityLabel={accessibilityLabel}
|
||||
accessibilityRole='img'
|
||||
accessible={accessible}
|
||||
onLayout={onLayout}
|
||||
ref='root'
|
||||
style={[
|
||||
styles.initial,
|
||||
@@ -117,7 +121,7 @@ class Image extends Component {
|
||||
]}
|
||||
testID={testID}
|
||||
>
|
||||
{createNativeComponent({ component: 'img', src: displayImage, style: styles.img })}
|
||||
{createReactDOMComponent({ component: 'img', src: displayImage, style: styles.img })}
|
||||
{children ? (
|
||||
<View children={children} pointerEvents='box-none' style={styles.children} />
|
||||
) : null}
|
||||
@@ -176,6 +180,8 @@ class Image extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
applyNativeMethods(Image)
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
initial: {
|
||||
alignSelf: 'flex-start',
|
||||
@@ -201,6 +207,10 @@ const styles = StyleSheet.create({
|
||||
})
|
||||
|
||||
const resizeModeStyles = StyleSheet.create({
|
||||
center: {
|
||||
backgroundSize: 'auto',
|
||||
backgroundPosition: 'center'
|
||||
},
|
||||
contain: {
|
||||
backgroundSize: 'contain'
|
||||
},
|
||||
@@ -210,6 +220,10 @@ const resizeModeStyles = StyleSheet.create({
|
||||
none: {
|
||||
backgroundSize: 'auto'
|
||||
},
|
||||
repeat: {
|
||||
backgroundSize: 'auto',
|
||||
backgroundRepeat: 'repeat'
|
||||
},
|
||||
stretch: {
|
||||
backgroundSize: '100% 100%'
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import NativeMethodsDecorator from '../../modules/NativeMethodsDecorator'
|
||||
import applyNativeMethods from '../../modules/applyNativeMethods'
|
||||
import React, { Component } from 'react'
|
||||
import ScrollView from '../ScrollView'
|
||||
import ListViewDataSource from './ListViewDataSource'
|
||||
import ListViewPropTypes from './ListViewPropTypes'
|
||||
import View from '../View'
|
||||
import pick from 'lodash/pick'
|
||||
|
||||
const SCROLLVIEW_REF = 'listviewscroll'
|
||||
|
||||
@NativeMethodsDecorator
|
||||
class ListView extends Component {
|
||||
static propTypes = ListViewPropTypes;
|
||||
|
||||
@@ -65,7 +66,7 @@ class ListView extends Component {
|
||||
if (renderSectionHeader) {
|
||||
const section = dataSource.getSectionHeaderData(sectionIdx)
|
||||
const key = 's_' + sectionId
|
||||
const child = <div key={key}>{renderSectionHeader(section, sectionId)}</div>
|
||||
const child = <View key={key}>{renderSectionHeader(section, sectionId)}</View>
|
||||
children.push(child)
|
||||
}
|
||||
|
||||
@@ -74,7 +75,7 @@ class ListView extends Component {
|
||||
const rowId = rows[rowIdx]
|
||||
const row = dataSource.getRowData(sectionIdx, rowIdx)
|
||||
const key = 'r_' + sectionId + '_' + rowId
|
||||
const child = <div key={key}>{renderRow(row, sectionId, rowId, this.onRowHighlighted)}</div>
|
||||
const child = <View key={key}>{renderRow(row, sectionId, rowId, this.onRowHighlighted)}</View>
|
||||
children.push(child)
|
||||
|
||||
// render optional separator
|
||||
@@ -89,15 +90,14 @@ class ListView extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
renderScrollComponent,
|
||||
...props
|
||||
} = this.props
|
||||
const props = pick(ScrollView.propTypes, this.props)
|
||||
|
||||
return React.cloneElement(renderScrollComponent(props), {
|
||||
return React.cloneElement(this.props.renderScrollComponent(props), {
|
||||
ref: SCROLLVIEW_REF
|
||||
}, header, children, footer)
|
||||
}
|
||||
}
|
||||
|
||||
applyNativeMethods(ListView)
|
||||
|
||||
module.exports = ListView
|
||||
|
||||
@@ -1,153 +0,0 @@
|
||||
/**
|
||||
* Copyright 2015-present, Nicolas Gallagher
|
||||
* Copyright 2004-present, Facebook Inc.
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import Platform from '../../apis/Platform'
|
||||
import React, { Component, PropTypes } from 'react'
|
||||
import StyleSheet from '../../apis/StyleSheet'
|
||||
import View from '../View'
|
||||
|
||||
let _portalRef: any
|
||||
// unique identifiers for modals
|
||||
let lastUsedTag = 0
|
||||
|
||||
/**
|
||||
* A container that renders all the modals on top of everything else in the application.
|
||||
*/
|
||||
class Portal extends Component {
|
||||
static propTypes = {
|
||||
onModalVisibilityChanged: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new unique tag.
|
||||
*/
|
||||
static allocateTag(): string {
|
||||
return `__modal_${++lastUsedTag}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a new modal.
|
||||
*/
|
||||
static showModal(tag: string, component: any) {
|
||||
if (!_portalRef) {
|
||||
console.error('Calling showModal but no "Portal" has been rendered.')
|
||||
return
|
||||
}
|
||||
_portalRef._showModal(tag, component)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a modal from the collection of modals to be rendered.
|
||||
*/
|
||||
static closeModal(tag: string) {
|
||||
if (!_portalRef) {
|
||||
console.error('Calling closeModal but no "Portal" has been rendered.')
|
||||
return
|
||||
}
|
||||
_portalRef._closeModal(tag)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of all the open modals, as identified by their tag string.
|
||||
*/
|
||||
static getOpenModals(): Array<string> {
|
||||
if (!_portalRef) {
|
||||
console.error('Calling getOpenModals but no "Portal" has been rendered.')
|
||||
return []
|
||||
}
|
||||
return _portalRef._getOpenModals()
|
||||
}
|
||||
|
||||
static notifyAccessibilityService() {
|
||||
if (!_portalRef) {
|
||||
console.error('Calling closeModal but no "Portal" has been rendered.')
|
||||
return
|
||||
}
|
||||
_portalRef._notifyAccessibilityService()
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = { modals: {} }
|
||||
this._closeModal = this._closeModal.bind(this)
|
||||
this._getOpenModals = this._getOpenModals.bind(this)
|
||||
this._showModal = this._showModal.bind(this)
|
||||
}
|
||||
|
||||
render() {
|
||||
_portalRef = this
|
||||
if (!this.state.modals) { return null }
|
||||
const modals = []
|
||||
for (const tag in this.state.modals) {
|
||||
modals.push(this.state.modals[tag])
|
||||
}
|
||||
if (modals.length === 0) { return null }
|
||||
|
||||
return (
|
||||
<View style={styles.root}>
|
||||
{modals}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
_closeModal(tag: string) {
|
||||
if (!this.state.modals.hasOwnProperty(tag)) {
|
||||
return
|
||||
}
|
||||
// We are about to close last modal, so Portal will disappear.
|
||||
// Let's enable accessibility for application view.
|
||||
if (this._getOpenModals().length === 1) {
|
||||
this.props.onModalVisibilityChanged(false)
|
||||
}
|
||||
// This way state is chained through multiple calls to
|
||||
// _showModal, _closeModal correctly.
|
||||
this.setState((state) => {
|
||||
const modals = state.modals
|
||||
delete modals[tag]
|
||||
return { modals }
|
||||
})
|
||||
}
|
||||
|
||||
_getOpenModals(): Array<string> {
|
||||
return Object.keys(this.state.modals)
|
||||
}
|
||||
|
||||
_notifyAccessibilityService() {
|
||||
if (Platform.OS === 'web') {
|
||||
// We need to send accessibility event in a new batch, as otherwise
|
||||
// TextViews have no text set at the moment of populating event.
|
||||
}
|
||||
}
|
||||
|
||||
_showModal(tag: string, component: any) {
|
||||
// We are about to open first modal, so Portal will appear.
|
||||
// Let's disable accessibility for background view on Android.
|
||||
if (this._getOpenModals().length === 0) {
|
||||
this.props.onModalVisibilityChanged(true)
|
||||
}
|
||||
// This way state is chained through multiple calls to
|
||||
// _showModal, _closeModal correctly.
|
||||
this.setState((state) => {
|
||||
const modals = state.modals
|
||||
modals[tag] = component
|
||||
return { modals }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = Portal
|
||||
@@ -16,7 +16,11 @@ import View from '../View'
|
||||
export default class ScrollViewBase extends Component {
|
||||
static propTypes = {
|
||||
...View.propTypes,
|
||||
onMomentumScrollBegin: PropTypes.func,
|
||||
onMomentumScrollEnd: PropTypes.func,
|
||||
onScroll: PropTypes.func,
|
||||
onScrollBeginDrag: PropTypes.func,
|
||||
onScrollEndDrag: PropTypes.func,
|
||||
onTouchMove: PropTypes.func,
|
||||
onWheel: PropTypes.func,
|
||||
scrollEnabled: PropTypes.bool,
|
||||
@@ -30,12 +34,10 @@ export default class ScrollViewBase extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this._debouncedOnScrollEnd = debounce(this._handleScrollEnd, 100)
|
||||
this._handlePreventableScrollEvent = this._handlePreventableScrollEvent.bind(this)
|
||||
this._handleScroll = this._handleScroll.bind(this)
|
||||
this._state = { isScrolling: false }
|
||||
}
|
||||
|
||||
_handlePreventableScrollEvent(handler) {
|
||||
_handlePreventableScrollEvent = (handler) => {
|
||||
return (e) => {
|
||||
if (!this.props.scrollEnabled) {
|
||||
e.preventDefault()
|
||||
@@ -45,7 +47,7 @@ export default class ScrollViewBase extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
_handleScroll(e) {
|
||||
_handleScroll = (e) => {
|
||||
const { scrollEventThrottle } = this.props
|
||||
// A scroll happened, so the scroll bumps the debounce.
|
||||
this._debouncedOnScrollEnd(e)
|
||||
@@ -83,9 +85,14 @@ export default class ScrollViewBase extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
onMomentumScrollBegin, onMomentumScrollEnd, onScrollBeginDrag, onScrollEndDrag, scrollEnabled, scrollEventThrottle, // eslint-disable-line
|
||||
...other
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<View
|
||||
{...this.props}
|
||||
{...other}
|
||||
onScroll={this._handleScroll}
|
||||
onTouchMove={this._handlePreventableScrollEvent(this.props.onTouchMove)}
|
||||
onWheel={this._handlePreventableScrollEvent(this.props.onWheel)}
|
||||
|
||||
@@ -13,7 +13,7 @@ import ReactDOM from 'react-dom'
|
||||
import ScrollResponder from '../../modules/ScrollResponder'
|
||||
import ScrollViewBase from './ScrollViewBase'
|
||||
import StyleSheet from '../../apis/StyleSheet'
|
||||
import StyleSheetPropType from '../../apis/StyleSheet/StyleSheetPropType'
|
||||
import StyleSheetPropType from '../../propTypes/StyleSheetPropType'
|
||||
import View from '../View'
|
||||
import ViewStylePropTypes from '../View/ViewStylePropTypes'
|
||||
|
||||
@@ -121,16 +121,15 @@ const ScrollView = React.createClass({
|
||||
},
|
||||
|
||||
render() {
|
||||
const scrollViewStyle = [
|
||||
styles.base,
|
||||
this.props.horizontal && styles.baseHorizontal
|
||||
]
|
||||
|
||||
const contentContainerStyle = [
|
||||
styles.contentContainer,
|
||||
this.props.horizontal && styles.contentContainerHorizontal,
|
||||
this.props.contentContainerStyle
|
||||
]
|
||||
const {
|
||||
contentContainerStyle,
|
||||
horizontal,
|
||||
keyboardDismissMode, // eslint-disable-line
|
||||
onContentSizeChange,
|
||||
onScroll, // eslint-disable-line
|
||||
refreshControl,
|
||||
...other
|
||||
} = this.props
|
||||
|
||||
if (process.env.NODE_ENV !== 'production' && this.props.style) {
|
||||
const style = StyleSheet.flatten(this.props.style)
|
||||
@@ -143,7 +142,7 @@ const ScrollView = React.createClass({
|
||||
}
|
||||
|
||||
let contentSizeChangeProps = {}
|
||||
if (this.props.onContentSizeChange) {
|
||||
if (onContentSizeChange) {
|
||||
contentSizeChangeProps = {
|
||||
onLayout: this._handleContentOnLayout
|
||||
}
|
||||
@@ -155,13 +154,21 @@ const ScrollView = React.createClass({
|
||||
children={this.props.children}
|
||||
collapsable={false}
|
||||
ref={INNERVIEW}
|
||||
style={contentContainerStyle}
|
||||
style={[
|
||||
styles.contentContainer,
|
||||
horizontal && styles.contentContainerHorizontal,
|
||||
contentContainerStyle
|
||||
]}
|
||||
/>
|
||||
)
|
||||
|
||||
const props = {
|
||||
...this.props,
|
||||
style: [scrollViewStyle, this.props.style],
|
||||
...other,
|
||||
style: [
|
||||
styles.base,
|
||||
horizontal && styles.baseHorizontal,
|
||||
this.props.style
|
||||
],
|
||||
onTouchStart: this.scrollResponderHandleTouchStart,
|
||||
onTouchMove: this.scrollResponderHandleTouchMove,
|
||||
onTouchEnd: this.scrollResponderHandleTouchEnd,
|
||||
@@ -187,7 +194,6 @@ const ScrollView = React.createClass({
|
||||
'ScrollViewClass must not be undefined'
|
||||
)
|
||||
|
||||
var refreshControl = this.props.refreshControl
|
||||
if (refreshControl) {
|
||||
return React.cloneElement(
|
||||
refreshControl,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { PropTypes } from 'react'
|
||||
import ColorPropType from '../../apis/StyleSheet/ColorPropType'
|
||||
import ColorPropType from '../../propTypes/ColorPropType'
|
||||
import ViewStylePropTypes from '../View/ViewStylePropTypes'
|
||||
|
||||
const { number, oneOf, oneOfType, string } = PropTypes
|
||||
|
||||
@@ -1,27 +1,41 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
import * as utils from '../../../modules/specHelpers'
|
||||
import assert from 'assert'
|
||||
import React from 'react'
|
||||
import ReactTestUtils from 'react-addons-test-utils'
|
||||
|
||||
import Text from '../'
|
||||
import { mount, shallow } from 'enzyme'
|
||||
|
||||
suite('components/Text', () => {
|
||||
test('prop "children"', () => {
|
||||
const children = 'children'
|
||||
const result = utils.shallowRender(<Text>{children}</Text>)
|
||||
assert.equal(result.props.children, children)
|
||||
const text = shallow(<Text>{children}</Text>)
|
||||
assert.equal(text.prop('children'), children)
|
||||
})
|
||||
|
||||
test('prop "numberOfLines"')
|
||||
|
||||
test('prop "onLayout"', (done) => {
|
||||
mount(<Text onLayout={onLayout} />)
|
||||
function onLayout(e) {
|
||||
const { layout } = e.nativeEvent
|
||||
assert.deepEqual(layout, { x: 0, y: 0, width: 0, height: 0 })
|
||||
done()
|
||||
}
|
||||
})
|
||||
|
||||
test('prop "onPress"', (done) => {
|
||||
const dom = utils.renderToDOM(<Text onPress={onPress} />)
|
||||
ReactTestUtils.Simulate.click(dom)
|
||||
const text = mount(<Text onPress={onPress} />)
|
||||
text.simulate('click')
|
||||
function onPress(e) {
|
||||
assert.ok(e.nativeEvent)
|
||||
done()
|
||||
}
|
||||
})
|
||||
|
||||
test('prop "selectable"', () => {
|
||||
let text = shallow(<Text />)
|
||||
assert.equal(text.prop('style').userSelect, undefined)
|
||||
text = shallow(<Text selectable={false} />)
|
||||
assert.equal(text.prop('style').userSelect, 'none')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,54 +1,62 @@
|
||||
import createNativeComponent from '../../modules/createNativeComponent'
|
||||
import NativeMethodsDecorator from '../../modules/NativeMethodsDecorator'
|
||||
import applyLayout from '../../modules/applyLayout'
|
||||
import applyNativeMethods from '../../modules/applyNativeMethods'
|
||||
import createReactDOMComponent from '../../modules/createReactDOMComponent'
|
||||
import { Component, PropTypes } from 'react'
|
||||
import StyleSheet from '../../apis/StyleSheet'
|
||||
import StyleSheetPropType from '../../apis/StyleSheet/StyleSheetPropType'
|
||||
import StyleSheetPropType from '../../propTypes/StyleSheetPropType'
|
||||
import TextStylePropTypes from './TextStylePropTypes'
|
||||
|
||||
@NativeMethodsDecorator
|
||||
class Text extends Component {
|
||||
static displayName = 'Text'
|
||||
|
||||
static propTypes = {
|
||||
accessibilityLabel: createNativeComponent.propTypes.accessibilityLabel,
|
||||
accessibilityRole: createNativeComponent.propTypes.accessibilityRole,
|
||||
accessible: createNativeComponent.propTypes.accessible,
|
||||
accessibilityLabel: createReactDOMComponent.propTypes.accessibilityLabel,
|
||||
accessibilityRole: PropTypes.oneOf([ 'heading', 'link' ]),
|
||||
accessible: createReactDOMComponent.propTypes.accessible,
|
||||
children: PropTypes.any,
|
||||
numberOfLines: PropTypes.number,
|
||||
onLayout: PropTypes.func,
|
||||
onPress: PropTypes.func,
|
||||
selectable: PropTypes.bool,
|
||||
style: StyleSheetPropType(TextStylePropTypes),
|
||||
testID: createNativeComponent.propTypes.testID
|
||||
testID: createReactDOMComponent.propTypes.testID
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
accessible: true
|
||||
accessible: true,
|
||||
selectable: true
|
||||
};
|
||||
|
||||
_onPress = (e) => {
|
||||
if (this.props.onPress) this.props.onPress(e)
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
numberOfLines,
|
||||
/* eslint-disable no-unused-vars */
|
||||
onPress,
|
||||
/* eslint-enable no-unused-vars */
|
||||
onLayout, // eslint-disable-line
|
||||
onPress, // eslint-disable-line
|
||||
selectable,
|
||||
style,
|
||||
...other
|
||||
} = this.props
|
||||
|
||||
return createNativeComponent({
|
||||
return createReactDOMComponent({
|
||||
...other,
|
||||
component: 'span',
|
||||
onClick: this._onPress,
|
||||
style: [
|
||||
styles.initial,
|
||||
style,
|
||||
!selectable && styles.notSelectable,
|
||||
numberOfLines === 1 && styles.singleLineStyle
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
_onPress = (e) => {
|
||||
if (this.props.onPress) this.props.onPress(e)
|
||||
}
|
||||
}
|
||||
|
||||
applyLayout(applyNativeMethods(Text))
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
initial: {
|
||||
color: 'inherit',
|
||||
@@ -59,6 +67,9 @@ const styles = StyleSheet.create({
|
||||
textDecorationLine: 'none',
|
||||
wordWrap: 'break-word'
|
||||
},
|
||||
notSelectable: {
|
||||
userSelect: 'none'
|
||||
},
|
||||
singleLineStyle: {
|
||||
maxWidth: '100%',
|
||||
overflow: 'hidden',
|
||||
|
||||
@@ -1,88 +1,96 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
import * as utils from '../../../modules/specHelpers'
|
||||
import assert from 'assert'
|
||||
import React from 'react'
|
||||
import ReactTestUtils from 'react-addons-test-utils'
|
||||
import StyleSheet from '../../../apis/StyleSheet'
|
||||
import TextareaAutosize from 'react-textarea-autosize'
|
||||
import TextInput from '..'
|
||||
import { mount, shallow } from 'enzyme'
|
||||
|
||||
import TextInput from '../'
|
||||
const placeholderText = 'placeholderText'
|
||||
const findNativeInput = (wrapper) => wrapper.find('input')
|
||||
const findNativeTextarea = (wrapper) => wrapper.find(TextareaAutosize)
|
||||
const findPlaceholder = (wrapper) => wrapper.find({ children: placeholderText })
|
||||
|
||||
const findInput = (dom) => dom.querySelector('input, textarea')
|
||||
const findShallowInput = (vdom) => vdom.props.children.props.children[0]
|
||||
const findShallowPlaceholder = (vdom) => vdom.props.children.props.children[1]
|
||||
const testIfDocumentIsFocused = (message, fn) => {
|
||||
if (document.hasFocus && document.hasFocus()) {
|
||||
test(message, fn)
|
||||
} else {
|
||||
test.skip(`${message} – document is not focused`)
|
||||
}
|
||||
}
|
||||
|
||||
suite('components/TextInput', () => {
|
||||
test('prop "autoComplete"', () => {
|
||||
// off
|
||||
let input = findInput(utils.renderToDOM(<TextInput />))
|
||||
assert.equal(input.getAttribute('autocomplete'), undefined)
|
||||
let input = findNativeInput(shallow(<TextInput />))
|
||||
assert.equal(input.prop('autoComplete'), undefined)
|
||||
// on
|
||||
input = findInput(utils.renderToDOM(<TextInput autoComplete />))
|
||||
assert.equal(input.getAttribute('autocomplete'), 'on')
|
||||
input = findNativeInput(shallow(<TextInput autoComplete />))
|
||||
assert.equal(input.prop('autoComplete'), 'on')
|
||||
})
|
||||
|
||||
test.skip('prop "autoFocus"', () => {
|
||||
test('prop "autoFocus"', () => {
|
||||
// false
|
||||
let input = findInput(utils.renderToDOM(<TextInput />))
|
||||
assert.deepEqual(document.activeElement, document.body)
|
||||
let input = findNativeInput(mount(<TextInput />))
|
||||
assert.equal(input.prop('autoFocus'), undefined)
|
||||
// true
|
||||
input = findInput(utils.renderToDOM(<TextInput autoFocus />))
|
||||
assert.deepEqual(document.activeElement, input)
|
||||
input = findNativeInput(mount(<TextInput autoFocus />))
|
||||
assert.equal(input.prop('autoFocus'), true)
|
||||
})
|
||||
|
||||
utils.testIfDocumentFocused('prop "clearTextOnFocus"', () => {
|
||||
testIfDocumentIsFocused('prop "clearTextOnFocus"', () => {
|
||||
const defaultValue = 'defaultValue'
|
||||
// false
|
||||
let input = findInput(utils.renderAndInject(<TextInput defaultValue={defaultValue} />))
|
||||
input.focus()
|
||||
assert.equal(input.value, defaultValue)
|
||||
let input = findNativeInput(mount(<TextInput defaultValue={defaultValue} />))
|
||||
input.simulate('focus')
|
||||
assert.equal(input.node.value, defaultValue)
|
||||
// true
|
||||
input = findInput(utils.renderAndInject(<TextInput clearTextOnFocus defaultValue={defaultValue} />))
|
||||
input.focus()
|
||||
assert.equal(input.value, '')
|
||||
input = findNativeInput(mount(<TextInput clearTextOnFocus defaultValue={defaultValue} />))
|
||||
input.simulate('focus')
|
||||
assert.equal(input.node.value, '')
|
||||
})
|
||||
|
||||
test('prop "defaultValue"', () => {
|
||||
const defaultValue = 'defaultValue'
|
||||
const input = findShallowInput(utils.shallowRender(<TextInput defaultValue={defaultValue} />))
|
||||
assert.equal(input.props.defaultValue, defaultValue)
|
||||
const input = findNativeInput(shallow(<TextInput defaultValue={defaultValue} />))
|
||||
assert.equal(input.prop('defaultValue'), defaultValue)
|
||||
})
|
||||
|
||||
test('prop "editable"', () => {
|
||||
// true
|
||||
let input = findInput(utils.renderToDOM(<TextInput />))
|
||||
assert.equal(input.getAttribute('readonly'), undefined)
|
||||
let input = findNativeInput(shallow(<TextInput />))
|
||||
assert.equal(input.prop('readOnly'), false)
|
||||
// false
|
||||
input = findInput(utils.renderToDOM(<TextInput editable={false} />))
|
||||
assert.equal(input.getAttribute('readonly'), '')
|
||||
input = findNativeInput(shallow(<TextInput editable={false} />))
|
||||
assert.equal(input.prop('readOnly'), true)
|
||||
})
|
||||
|
||||
test('prop "keyboardType"', () => {
|
||||
// default
|
||||
let input = findInput(utils.renderToDOM(<TextInput />))
|
||||
assert.equal(input.getAttribute('type'), undefined)
|
||||
input = findInput(utils.renderToDOM(<TextInput keyboardType='default' />))
|
||||
assert.equal(input.getAttribute('type'), undefined)
|
||||
let input = findNativeInput(shallow(<TextInput />))
|
||||
assert.equal(input.prop('type'), undefined)
|
||||
input = findNativeInput(shallow(<TextInput keyboardType='default' />))
|
||||
assert.equal(input.prop('type'), undefined)
|
||||
// email-address
|
||||
input = findInput(utils.renderToDOM(<TextInput keyboardType='email-address' />))
|
||||
assert.equal(input.getAttribute('type'), 'email')
|
||||
input = findNativeInput(shallow(<TextInput keyboardType='email-address' />))
|
||||
assert.equal(input.prop('type'), 'email')
|
||||
// numeric
|
||||
input = findInput(utils.renderToDOM(<TextInput keyboardType='numeric' />))
|
||||
assert.equal(input.getAttribute('type'), 'number')
|
||||
input = findNativeInput(shallow(<TextInput keyboardType='numeric' />))
|
||||
assert.equal(input.prop('type'), 'number')
|
||||
// phone-pad
|
||||
input = findInput(utils.renderToDOM(<TextInput keyboardType='phone-pad' />))
|
||||
assert.equal(input.getAttribute('type'), 'tel')
|
||||
input = findNativeInput(shallow(<TextInput keyboardType='phone-pad' />))
|
||||
assert.equal(input.prop('type'), 'tel')
|
||||
// url
|
||||
input = findInput(utils.renderToDOM(<TextInput keyboardType='url' />))
|
||||
assert.equal(input.getAttribute('type'), 'url')
|
||||
input = findNativeInput(shallow(<TextInput keyboardType='url' />))
|
||||
assert.equal(input.prop('type'), 'url')
|
||||
})
|
||||
|
||||
test('prop "maxLength"', () => {
|
||||
let input = findInput(utils.renderToDOM(<TextInput />))
|
||||
assert.equal(input.getAttribute('maxlength'), undefined)
|
||||
input = findInput(utils.renderToDOM(<TextInput maxLength={10} />))
|
||||
assert.equal(input.getAttribute('maxlength'), '10')
|
||||
let input = findNativeInput(shallow(<TextInput />))
|
||||
assert.equal(input.prop('maxLength'), undefined)
|
||||
input = findNativeInput(shallow(<TextInput maxLength={10} />))
|
||||
assert.equal(input.prop('maxLength'), '10')
|
||||
})
|
||||
|
||||
test('prop "maxNumberOfLines"', () => {
|
||||
@@ -92,45 +100,45 @@ suite('components/TextInput', () => {
|
||||
return str
|
||||
}
|
||||
|
||||
const result = utils.shallowRender(
|
||||
const input = findNativeTextarea(shallow(
|
||||
<TextInput
|
||||
maxNumberOfLines={3}
|
||||
multiline
|
||||
value={generateValue()}
|
||||
/>
|
||||
)
|
||||
assert.equal(findShallowInput(result).props.maxRows, 3)
|
||||
))
|
||||
assert.equal(input.prop('maxRows'), 3)
|
||||
})
|
||||
|
||||
test('prop "multiline"', () => {
|
||||
// false
|
||||
let input = findInput(utils.renderToDOM(<TextInput />))
|
||||
assert.equal(input.tagName, 'INPUT')
|
||||
let input = findNativeInput(shallow(<TextInput />))
|
||||
assert.equal(input.length, 1)
|
||||
// true
|
||||
input = findInput(utils.renderToDOM(<TextInput multiline />))
|
||||
assert.equal(input.tagName, 'TEXTAREA')
|
||||
input = findNativeTextarea(shallow(<TextInput multiline />))
|
||||
assert.equal(input.length, 1)
|
||||
})
|
||||
|
||||
test('prop "numberOfLines"', () => {
|
||||
// missing multiline
|
||||
let input = findInput(utils.renderToDOM(<TextInput numberOfLines={2} />))
|
||||
assert.equal(input.tagName, 'INPUT')
|
||||
let input = findNativeInput(shallow(<TextInput numberOfLines={2} />))
|
||||
assert.equal(input.length, 1)
|
||||
// with multiline
|
||||
input = findInput(utils.renderAndInject(<TextInput multiline numberOfLines={2} />))
|
||||
assert.equal(input.tagName, 'TEXTAREA')
|
||||
input = findNativeTextarea(shallow(<TextInput multiline numberOfLines={2} />))
|
||||
assert.equal(input.length, 1)
|
||||
|
||||
const result = utils.shallowRender(
|
||||
input = findNativeTextarea(shallow(
|
||||
<TextInput
|
||||
multiline
|
||||
numberOfLines={3}
|
||||
/>
|
||||
)
|
||||
assert.equal(findShallowInput(result).props.minRows, 3)
|
||||
))
|
||||
assert.equal(input.prop('minRows'), 3)
|
||||
})
|
||||
|
||||
test('prop "onBlur"', (done) => {
|
||||
const input = findInput(utils.renderToDOM(<TextInput onBlur={onBlur} />))
|
||||
ReactTestUtils.Simulate.blur(input)
|
||||
const input = findNativeInput(mount(<TextInput onBlur={onBlur} />))
|
||||
input.simulate('blur')
|
||||
function onBlur(e) {
|
||||
assert.ok(e)
|
||||
done()
|
||||
@@ -138,8 +146,8 @@ suite('components/TextInput', () => {
|
||||
})
|
||||
|
||||
test('prop "onChange"', (done) => {
|
||||
const input = findInput(utils.renderToDOM(<TextInput onChange={onChange} />))
|
||||
ReactTestUtils.Simulate.change(input)
|
||||
const input = findNativeInput(mount(<TextInput onChange={onChange} />))
|
||||
input.simulate('change')
|
||||
function onChange(e) {
|
||||
assert.ok(e)
|
||||
done()
|
||||
@@ -148,8 +156,8 @@ suite('components/TextInput', () => {
|
||||
|
||||
test('prop "onChangeText"', (done) => {
|
||||
const newText = 'newText'
|
||||
const input = findInput(utils.renderToDOM(<TextInput onChangeText={onChangeText} />))
|
||||
ReactTestUtils.Simulate.change(input, { target: { value: newText } })
|
||||
const input = findNativeInput(mount(<TextInput onChangeText={onChangeText} />))
|
||||
input.simulate('change', { target: { value: newText } })
|
||||
function onChangeText(text) {
|
||||
assert.equal(text, newText)
|
||||
done()
|
||||
@@ -157,8 +165,8 @@ suite('components/TextInput', () => {
|
||||
})
|
||||
|
||||
test('prop "onFocus"', (done) => {
|
||||
const input = findInput(utils.renderToDOM(<TextInput onFocus={onFocus} />))
|
||||
ReactTestUtils.Simulate.focus(input)
|
||||
const input = findNativeInput(mount(<TextInput onFocus={onFocus} />))
|
||||
input.simulate('focus')
|
||||
function onFocus(e) {
|
||||
assert.ok(e)
|
||||
done()
|
||||
@@ -168,8 +176,8 @@ suite('components/TextInput', () => {
|
||||
test('prop "onLayout"')
|
||||
|
||||
test('prop "onSelectionChange"', (done) => {
|
||||
const input = findInput(utils.renderAndInject(<TextInput defaultValue='12345' onSelectionChange={onSelectionChange} />))
|
||||
ReactTestUtils.Simulate.select(input, { target: { selectionStart: 0, selectionEnd: 3 } })
|
||||
const input = findNativeInput(mount(<TextInput defaultValue='12345' onSelectionChange={onSelectionChange} />))
|
||||
input.simulate('select', { target: { selectionStart: 0, selectionEnd: 3 } })
|
||||
function onSelectionChange(e) {
|
||||
assert.equal(e.selectionEnd, 3)
|
||||
assert.equal(e.selectionStart, 0)
|
||||
@@ -178,46 +186,59 @@ suite('components/TextInput', () => {
|
||||
})
|
||||
|
||||
test('prop "placeholder"', () => {
|
||||
const placeholder = 'placeholder'
|
||||
const result = findShallowPlaceholder(utils.shallowRender(<TextInput placeholder={placeholder} />))
|
||||
assert.equal(result.props.children.props.children, placeholder)
|
||||
let textInput = shallow(<TextInput />)
|
||||
assert.equal(findPlaceholder(textInput).length, 0)
|
||||
|
||||
textInput = shallow(<TextInput placeholder={placeholderText} />)
|
||||
assert.equal(findPlaceholder(textInput).length, 1)
|
||||
})
|
||||
|
||||
test('prop "placeholderTextColor"', () => {
|
||||
const placeholder = 'placeholder'
|
||||
let placeholderElement = findPlaceholder(shallow(<TextInput placeholder={placeholderText} />))
|
||||
assert.equal(StyleSheet.flatten(placeholderElement.prop('style')).color, 'darkgray')
|
||||
|
||||
let result = findShallowPlaceholder(utils.shallowRender(<TextInput placeholder={placeholder} />))
|
||||
assert.equal(StyleSheet.flatten(result.props.children.props.style).color, 'darkgray')
|
||||
|
||||
result = findShallowPlaceholder(utils.shallowRender(<TextInput placeholder={placeholder} placeholderTextColor='red' />))
|
||||
assert.equal(StyleSheet.flatten(result.props.children.props.style).color, 'red')
|
||||
placeholderElement = findPlaceholder(shallow(<TextInput placeholder={placeholderText} placeholderTextColor='red' />))
|
||||
assert.equal(StyleSheet.flatten(placeholderElement.prop('style')).color, 'red')
|
||||
})
|
||||
|
||||
test('prop "secureTextEntry"', () => {
|
||||
let input = findInput(utils.renderToDOM(<TextInput secureTextEntry />))
|
||||
assert.equal(input.getAttribute('type'), 'password')
|
||||
let input = findNativeInput(shallow(<TextInput secureTextEntry />))
|
||||
assert.equal(input.prop('type'), 'password')
|
||||
// ignored for multiline
|
||||
input = findInput(utils.renderToDOM(<TextInput multiline secureTextEntry />))
|
||||
assert.equal(input.getAttribute('type'), undefined)
|
||||
input = findNativeTextarea(shallow(<TextInput multiline secureTextEntry />))
|
||||
assert.equal(input.prop('type'), undefined)
|
||||
})
|
||||
|
||||
utils.testIfDocumentFocused('prop "selectTextOnFocus"', () => {
|
||||
testIfDocumentIsFocused('prop "selectTextOnFocus"', () => {
|
||||
const text = 'Text'
|
||||
// false
|
||||
let input = findInput(utils.renderAndInject(<TextInput defaultValue={text} />))
|
||||
input.focus()
|
||||
assert.equal(input.selectionEnd, 0)
|
||||
assert.equal(input.selectionStart, 0)
|
||||
let input = findNativeInput(mount(<TextInput defaultValue={text} />))
|
||||
input.node.focus()
|
||||
assert.equal(input.node.selectionEnd, 4)
|
||||
assert.equal(input.node.selectionStart, 4)
|
||||
// true
|
||||
input = findInput(utils.renderAndInject(<TextInput defaultValue={text} selectTextOnFocus />))
|
||||
input.focus()
|
||||
assert.equal(input.selectionEnd, 4)
|
||||
assert.equal(input.selectionStart, 0)
|
||||
// input = findNativeInput(mount(<TextInput defaultValue={text} selectTextOnFocus />))
|
||||
// input.node.focus()
|
||||
// assert.equal(input.node.selectionEnd, 4)
|
||||
// assert.equal(input.node.selectionStart, 0)
|
||||
})
|
||||
|
||||
test('prop "style"', () => {
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
borderWidth: 1,
|
||||
textAlign: 'center'
|
||||
}
|
||||
})
|
||||
const textInput = shallow(<TextInput style={styles.root} />)
|
||||
const input = findNativeInput(textInput)
|
||||
assert.equal(StyleSheet.flatten(textInput.prop('style')).borderWidth, 1, 'expected View styles to be applied to the "View"')
|
||||
assert.equal(input.prop('style').textAlign, 'center', 'expected Text styles to be applied to the "input"')
|
||||
})
|
||||
|
||||
test('prop "value"', () => {
|
||||
const value = 'value'
|
||||
const input = findShallowInput(utils.shallowRender(<TextInput value={value} />))
|
||||
assert.equal(input.props.value, value)
|
||||
const input = findNativeInput(shallow(<TextInput value={value} />))
|
||||
assert.equal(input.prop('value'), value)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import createNativeComponent from '../../modules/createNativeComponent'
|
||||
import NativeMethodsDecorator from '../../modules/NativeMethodsDecorator'
|
||||
import applyNativeMethods from '../../modules/applyNativeMethods'
|
||||
import createReactDOMComponent from '../../modules/createReactDOMComponent'
|
||||
import omit from 'lodash/omit'
|
||||
import pick from 'lodash/pick'
|
||||
import React, { Component, PropTypes } from 'react'
|
||||
@@ -8,12 +8,12 @@ import StyleSheet from '../../apis/StyleSheet'
|
||||
import Text from '../Text'
|
||||
import TextareaAutosize from 'react-textarea-autosize'
|
||||
import TextInputState from './TextInputState'
|
||||
import UIManager from '../../apis/UIManager'
|
||||
import View from '../View'
|
||||
import ViewStylePropTypes from '../View/ViewStylePropTypes'
|
||||
|
||||
const viewStyleProps = Object.keys(ViewStylePropTypes)
|
||||
|
||||
@NativeMethodsDecorator
|
||||
class TextInput extends Component {
|
||||
static propTypes = {
|
||||
...View.propTypes,
|
||||
@@ -68,14 +68,12 @@ class TextInput extends Component {
|
||||
}
|
||||
|
||||
setNativeProps(props) {
|
||||
this.refs.input.setNativeProps(props)
|
||||
UIManager.updateView(this.refs.input, props, this)
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
/* eslint-disable react/prop-types */
|
||||
accessibilityLabel,
|
||||
/* eslint-enable react/prop-types */
|
||||
accessibilityLabel, // eslint-disable-line
|
||||
autoComplete,
|
||||
autoFocus,
|
||||
defaultValue,
|
||||
@@ -85,6 +83,7 @@ class TextInput extends Component {
|
||||
maxNumberOfLines,
|
||||
multiline,
|
||||
numberOfLines,
|
||||
onLayout,
|
||||
onSelectionChange,
|
||||
placeholder,
|
||||
placeholderTextColor,
|
||||
@@ -122,8 +121,9 @@ class TextInput extends Component {
|
||||
// In order to support 'Text' styles on 'TextInput', we split the 'Text'
|
||||
// and 'View' styles and apply them to the 'Text' and 'View' components
|
||||
// used in the implementation
|
||||
const rootStyles = pick(style, viewStyleProps)
|
||||
const textStyles = omit(style, viewStyleProps)
|
||||
const flattenedStyle = StyleSheet.flatten(style)
|
||||
const rootStyles = pick(flattenedStyle, viewStyleProps)
|
||||
const textStyles = omit(flattenedStyle, viewStyleProps)
|
||||
|
||||
const propsCommon = {
|
||||
autoComplete: autoComplete && 'on',
|
||||
@@ -135,7 +135,7 @@ class TextInput extends Component {
|
||||
onFocus: this._handleFocus,
|
||||
onSelect: onSelectionChange && this._handleSelectionChange,
|
||||
readOnly: !editable,
|
||||
style: { ...styles.input, ...textStyles, outline: style.outline },
|
||||
style: [ styles.input, textStyles, { outline: style.outline } ],
|
||||
value
|
||||
}
|
||||
|
||||
@@ -171,11 +171,12 @@ class TextInput extends Component {
|
||||
<View
|
||||
accessibilityLabel={accessibilityLabel}
|
||||
onClick={this._handleClick}
|
||||
onLayout={onLayout}
|
||||
style={[ styles.initial, rootStyles ]}
|
||||
testID={testID}
|
||||
>
|
||||
<View style={styles.wrapper}>
|
||||
{createNativeComponent({ ...props, ref: 'input' })}
|
||||
{createReactDOMComponent({ ...props, ref: 'input' })}
|
||||
{optionalPlaceholder}
|
||||
</View>
|
||||
</View>
|
||||
@@ -232,13 +233,14 @@ class TextInput extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
applyNativeMethods(TextInput)
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
initial: {
|
||||
borderColor: 'black',
|
||||
borderWidth: 1
|
||||
borderColor: 'black'
|
||||
},
|
||||
wrapper: {
|
||||
flexGrow: 1
|
||||
flex: 1
|
||||
},
|
||||
input: {
|
||||
appearance: 'none',
|
||||
@@ -246,8 +248,9 @@ const styles = StyleSheet.create({
|
||||
borderWidth: 0,
|
||||
boxSizing: 'border-box',
|
||||
color: 'inherit',
|
||||
flexGrow: 1,
|
||||
flex: 1,
|
||||
font: 'inherit',
|
||||
minHeight: '100%', // center small inputs (fix #139)
|
||||
padding: 0,
|
||||
zIndex: 1
|
||||
},
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
/* @edit start */
|
||||
const BoundingDimensions = require('./BoundingDimensions');
|
||||
const keyMirror = require('fbjs/lib/keyMirror');
|
||||
const normalizeColor = require('../../apis/StyleSheet/normalizeColor');
|
||||
const normalizeColor = require('../../modules/normalizeColor');
|
||||
const Position = require('./Position');
|
||||
const React = require('react');
|
||||
const TouchEventUtils = require('fbjs/lib/TouchEventUtils');
|
||||
@@ -735,7 +735,7 @@ var Touchable = {
|
||||
if (!Touchable.TOUCH_TARGET_DEBUG) {
|
||||
return null;
|
||||
}
|
||||
if (!__DEV__) {
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
throw Error('Touchable.TOUCH_TARGET_DEBUG should not be enabled in prod!');
|
||||
}
|
||||
const debugHitSlopStyle = {};
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
'use strict';
|
||||
|
||||
var Animated = require('../../apis/Animated');
|
||||
var EdgeInsetsPropType = require('../../apis/StyleSheet/EdgeInsetsPropType');
|
||||
var EdgeInsetsPropType = require('../../propTypes/EdgeInsetsPropType');
|
||||
var NativeMethodsMixin = require('../../modules/NativeMethodsMixin');
|
||||
var React = require('react');
|
||||
var StyleSheet = require('../../apis/StyleSheet');
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
// Note (avik): add @flow when Flow supports spread properties in propTypes
|
||||
|
||||
var ColorPropType = require('../../apis/StyleSheet/ColorPropType');
|
||||
var ColorPropType = require('../../propTypes/ColorPropType');
|
||||
var NativeMethodsMixin = require('../../modules/NativeMethodsMixin');
|
||||
var React = require('react');
|
||||
var StyleSheet = require('../../apis/StyleSheet');
|
||||
@@ -106,11 +106,10 @@ var TouchableHighlight = React.createClass({
|
||||
backgroundColor: underlayColor,
|
||||
}
|
||||
},
|
||||
underlayProps: {
|
||||
style: {
|
||||
backgroundColor: style && style.backgroundColor || null
|
||||
}
|
||||
}
|
||||
underlayStyle: [
|
||||
INACTIVE_UNDERLAY_PROPS.style,
|
||||
props.style,
|
||||
]
|
||||
};
|
||||
},
|
||||
|
||||
@@ -206,7 +205,10 @@ var TouchableHighlight = React.createClass({
|
||||
this._hideTimeout = null;
|
||||
if (this._hasPressHandler() && this.refs[UNDERLAY_REF]) {
|
||||
this.refs[CHILD_REF].setNativeProps(INACTIVE_CHILD_PROPS);
|
||||
this.refs[UNDERLAY_REF].setNativeProps(this.state.underlayProps);
|
||||
this.refs[UNDERLAY_REF].setNativeProps({
|
||||
...INACTIVE_UNDERLAY_PROPS,
|
||||
style: this.state.underlayStyle,
|
||||
});
|
||||
this.props.onHideUnderlay && this.props.onHideUnderlay();
|
||||
}
|
||||
},
|
||||
@@ -245,7 +247,7 @@ var TouchableHighlight = React.createClass({
|
||||
onResponderRelease={this.touchableHandleResponderRelease}
|
||||
onResponderTerminate={this.touchableHandleResponderTerminate}
|
||||
ref={UNDERLAY_REF}
|
||||
style={[styles.root, this.props.style]}
|
||||
style={[ styles.root, this.state.underlayStyle ]}
|
||||
tabIndex='0'
|
||||
testID={this.props.testID}>
|
||||
{React.cloneElement(
|
||||
@@ -264,6 +266,9 @@ var UNDERLAY_REF = keyOf({underlayRef: null});
|
||||
var INACTIVE_CHILD_PROPS = {
|
||||
style: StyleSheet.create({x: {opacity: 1.0}}).x,
|
||||
};
|
||||
var INACTIVE_UNDERLAY_PROPS = {
|
||||
style: StyleSheet.create({x: {backgroundColor: 'transparent'}}).x,
|
||||
};
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
root: {
|
||||
|
||||
@@ -23,7 +23,7 @@ var Touchable = require('./Touchable');
|
||||
var TouchableWithoutFeedback = require('./TouchableWithoutFeedback');
|
||||
|
||||
var ensurePositiveDelayProps = require('./ensurePositiveDelayProps');
|
||||
var flattenStyle = require('../../apis/StyleSheet/flattenStyle');
|
||||
var flattenStyle = StyleSheet.flatten
|
||||
|
||||
type Event = Object;
|
||||
|
||||
@@ -166,7 +166,7 @@ var TouchableOpacity = React.createClass({
|
||||
render: function() {
|
||||
return (
|
||||
<Animated.View
|
||||
accessible={true}
|
||||
accessible={this.props.accessible !== false}
|
||||
accessibilityLabel={this.props.accessibilityLabel}
|
||||
accessibilityRole={this.props.accessibilityRole || 'button'}
|
||||
style={[styles.root, this.props.style, {opacity: this.state.anim}]}
|
||||
|
||||
@@ -12,12 +12,14 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var EdgeInsetsPropType = require('../../apis/StyleSheet/EdgeInsetsPropType');
|
||||
var EdgeInsetsPropType = require('../../propTypes/EdgeInsetsPropType');
|
||||
var React = require('react');
|
||||
var TimerMixin = require('react-timer-mixin');
|
||||
var Touchable = require('./Touchable');
|
||||
var View = require('../View');
|
||||
var ensurePositiveDelayProps = require('./ensurePositiveDelayProps');
|
||||
var warning = require('fbjs/lib/warning');
|
||||
var StyleSheet = require('../../apis/StyleSheet');
|
||||
|
||||
type Event = Object;
|
||||
|
||||
@@ -32,11 +34,11 @@ var PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30};
|
||||
* >
|
||||
* > If you wish to have several child components, wrap them in a View.
|
||||
*/
|
||||
var TouchableWithoutFeedback = React.createClass({
|
||||
const TouchableWithoutFeedback = React.createClass({
|
||||
mixins: [TimerMixin, Touchable.Mixin],
|
||||
|
||||
propTypes: {
|
||||
accessible: React.PropTypes.bool,
|
||||
accessible: View.propTypes.accessible,
|
||||
accessibilityLabel: View.propTypes.accessibilityLabel,
|
||||
accessibilityRole: View.propTypes.accessibilityRole,
|
||||
/**
|
||||
@@ -143,9 +145,25 @@ var TouchableWithoutFeedback = React.createClass({
|
||||
return this.props.delayPressOut || 0;
|
||||
},
|
||||
|
||||
render: function(): ReactElement {
|
||||
render: function(): ReactElement<any> {
|
||||
// Note(avik): remove dynamic typecast once Flow has been upgraded
|
||||
return (React: any).cloneElement(React.Children.only(this.props.children), {
|
||||
const child = React.Children.only(this.props.children);
|
||||
let children = child.props.children;
|
||||
warning(
|
||||
!child.type || child.type.displayName !== 'Text',
|
||||
'TouchableWithoutFeedback does not work well with Text children. Wrap children in a View instead. See ' +
|
||||
((child._owner && child._owner.getName && child._owner.getName()) || '<unknown>')
|
||||
);
|
||||
if (Touchable.TOUCH_TARGET_DEBUG && child.type && child.type.displayName === 'View') {
|
||||
if (!Array.isArray(children)) {
|
||||
children = [children];
|
||||
}
|
||||
children.push(Touchable.renderDebugView({color: 'red', hitSlop: this.props.hitSlop}));
|
||||
}
|
||||
const style = (Touchable.TOUCH_TARGET_DEBUG && child.type && child.type.displayName === 'Text') ?
|
||||
[styles.root, child.props.style, {color: 'red'}] :
|
||||
[styles.root, child.props.style];
|
||||
return (React: any).cloneElement(child, {
|
||||
accessible: this.props.accessible !== false,
|
||||
accessibilityLabel: this.props.accessibilityLabel,
|
||||
accessibilityRole: this.props.accessibilityRole,
|
||||
@@ -158,9 +176,17 @@ var TouchableWithoutFeedback = React.createClass({
|
||||
onResponderMove: this.touchableHandleResponderMove,
|
||||
onResponderRelease: this.touchableHandleResponderRelease,
|
||||
onResponderTerminate: this.touchableHandleResponderTerminate,
|
||||
style,
|
||||
children,
|
||||
tabIndex: '0'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
root: {
|
||||
cursor: 'pointer'
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = TouchableWithoutFeedback;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { PropTypes } from 'react'
|
||||
import BorderPropTypes from '../../apis/StyleSheet/BorderPropTypes'
|
||||
import ColorPropType from '../../apis/StyleSheet/ColorPropType'
|
||||
import LayoutPropTypes from '../../apis/StyleSheet/LayoutPropTypes'
|
||||
import TransformPropTypes from '../../apis/StyleSheet/TransformPropTypes'
|
||||
import BorderPropTypes from '../../propTypes/BorderPropTypes'
|
||||
import ColorPropType from '../../propTypes/ColorPropType'
|
||||
import LayoutPropTypes from '../../propTypes/LayoutPropTypes'
|
||||
import TransformPropTypes from '../../propTypes/TransformPropTypes'
|
||||
|
||||
const { number, oneOf, string } = PropTypes
|
||||
const autoOrHiddenOrVisible = oneOf([ 'auto', 'hidden', 'visible' ])
|
||||
|
||||
@@ -1,20 +1,42 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
import assert from 'assert'
|
||||
import includes from 'lodash/includes'
|
||||
import React from 'react'
|
||||
import { shallow } from 'enzyme'
|
||||
import View from '../'
|
||||
import { mount, shallow } from 'enzyme'
|
||||
|
||||
suite('components/View', () => {
|
||||
suite('rendered element', () => {
|
||||
test('is a "div" by default', () => {
|
||||
const view = shallow(<View />)
|
||||
assert.equal(view.is('div'), true)
|
||||
})
|
||||
|
||||
test('is a "span" when inside <View accessibilityRole="button" />', () => {
|
||||
const view = mount(<View accessibilityRole='button'><View /></View>)
|
||||
assert.equal(view.find('span').length, 1)
|
||||
})
|
||||
})
|
||||
|
||||
test('prop "children"', () => {
|
||||
const children = 'children'
|
||||
const children = <View testID='1' />
|
||||
const view = shallow(<View>{children}</View>)
|
||||
assert.equal(view.prop('children'), children)
|
||||
})
|
||||
|
||||
test('prop "onLayout"', (done) => {
|
||||
mount(<View onLayout={onLayout} />)
|
||||
function onLayout(e) {
|
||||
const { layout } = e.nativeEvent
|
||||
assert.deepEqual(layout, { x: 0, y: 0, width: 0, height: 0 })
|
||||
done()
|
||||
}
|
||||
})
|
||||
|
||||
test('prop "pointerEvents"', () => {
|
||||
const view = shallow(<View pointerEvents='box-only' />)
|
||||
assert.equal(view.prop('className'), '__style_pebo')
|
||||
assert.ok(includes(view.prop('className'), '__style_pebo') === true)
|
||||
})
|
||||
|
||||
test('prop "style"', () => {
|
||||
|
||||
@@ -1,21 +1,50 @@
|
||||
import createNativeComponent from '../../modules/createNativeComponent'
|
||||
import NativeMethodsDecorator from '../../modules/NativeMethodsDecorator'
|
||||
import normalizeNativeEvent from '../../apis/PanResponder/normalizeNativeEvent'
|
||||
import applyLayout from '../../modules/applyLayout'
|
||||
import applyNativeMethods from '../../modules/applyNativeMethods'
|
||||
import createReactDOMComponent from '../../modules/createReactDOMComponent'
|
||||
import EdgeInsetsPropType from '../../propTypes/EdgeInsetsPropType'
|
||||
import normalizeNativeEvent from '../../modules/normalizeNativeEvent'
|
||||
import { Component, PropTypes } from 'react'
|
||||
import StyleSheet from '../../apis/StyleSheet'
|
||||
import StyleSheetPropType from '../../apis/StyleSheet/StyleSheetPropType'
|
||||
import StyleSheetPropType from '../../propTypes/StyleSheetPropType'
|
||||
import ViewStylePropTypes from './ViewStylePropTypes'
|
||||
|
||||
@NativeMethodsDecorator
|
||||
const eventHandlerNames = [
|
||||
'onClick',
|
||||
'onClickCapture',
|
||||
'onMoveShouldSetResponder',
|
||||
'onMoveShouldSetResponderCapture',
|
||||
'onResponderGrant',
|
||||
'onResponderMove',
|
||||
'onResponderReject',
|
||||
'onResponderRelease',
|
||||
'onResponderTerminate',
|
||||
'onResponderTerminationRequest',
|
||||
'onStartShouldSetResponder',
|
||||
'onStartShouldSetResponderCapture',
|
||||
'onTouchCancel',
|
||||
'onTouchCancelCapture',
|
||||
'onTouchEnd',
|
||||
'onTouchEndCapture',
|
||||
'onTouchMove',
|
||||
'onTouchMoveCapture',
|
||||
'onTouchStart',
|
||||
'onTouchStartCapture'
|
||||
]
|
||||
|
||||
class View extends Component {
|
||||
static displayName = 'View'
|
||||
|
||||
static propTypes = {
|
||||
accessibilityLabel: createNativeComponent.propTypes.accessibilityLabel,
|
||||
accessibilityLiveRegion: createNativeComponent.propTypes.accessibilityLiveRegion,
|
||||
accessibilityRole: createNativeComponent.propTypes.accessibilityRole,
|
||||
accessible: createNativeComponent.propTypes.accessible,
|
||||
accessibilityLabel: createReactDOMComponent.propTypes.accessibilityLabel,
|
||||
accessibilityLiveRegion: createReactDOMComponent.propTypes.accessibilityLiveRegion,
|
||||
accessibilityRole: createReactDOMComponent.propTypes.accessibilityRole,
|
||||
accessible: createReactDOMComponent.propTypes.accessible,
|
||||
children: PropTypes.any,
|
||||
collapsable: PropTypes.bool,
|
||||
hitSlop: EdgeInsetsPropType,
|
||||
onClick: PropTypes.func,
|
||||
onClickCapture: PropTypes.func,
|
||||
onLayout: PropTypes.func,
|
||||
onMoveShouldSetResponder: PropTypes.func,
|
||||
onMoveShouldSetResponderCapture: PropTypes.func,
|
||||
onResponderGrant: PropTypes.func,
|
||||
@@ -36,7 +65,7 @@ class View extends Component {
|
||||
onTouchStartCapture: PropTypes.func,
|
||||
pointerEvents: PropTypes.oneOf(['auto', 'box-none', 'box-only', 'none']),
|
||||
style: StyleSheetPropType(ViewStylePropTypes),
|
||||
testID: createNativeComponent.propTypes.testID
|
||||
testID: createReactDOMComponent.propTypes.testID
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
@@ -44,13 +73,25 @@ class View extends Component {
|
||||
style: {}
|
||||
};
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
this._normalizeEventForHandler = this._normalizeEventForHandler.bind(this)
|
||||
static childContextTypes = {
|
||||
isInAButtonView: PropTypes.bool
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
isInAButtonView: PropTypes.bool
|
||||
};
|
||||
|
||||
getChildContext() {
|
||||
return {
|
||||
isInAButtonView: this.props.accessibilityRole === 'button'
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
collapsable, // eslint-disable-line
|
||||
hitSlop, // eslint-disable-line
|
||||
onLayout, // eslint-disable-line
|
||||
pointerEvents,
|
||||
style,
|
||||
...other
|
||||
@@ -58,43 +99,38 @@ class View extends Component {
|
||||
|
||||
const flattenedStyle = StyleSheet.flatten(style)
|
||||
const pointerEventsStyle = pointerEvents && { pointerEvents }
|
||||
// 'View' needs to set 'flexShrink:0' only when there is no 'flex' or 'flexShrink' style provided
|
||||
const needsFlexReset = flattenedStyle.flex == null && flattenedStyle.flexShrink == null
|
||||
|
||||
const normalizedEventHandlers = eventHandlerNames.reduce((handlerProps, handlerName) => {
|
||||
const handler = this.props[handlerName]
|
||||
if (typeof handler === 'function') {
|
||||
handlerProps[handlerName] = this._normalizeEventForHandler(handler)
|
||||
}
|
||||
return handlerProps
|
||||
}, {})
|
||||
|
||||
const props = {
|
||||
...other,
|
||||
onClick: this._normalizeEventForHandler(this.props.onClick),
|
||||
onClickCapture: this._normalizeEventForHandler(this.props.onClickCapture),
|
||||
onTouchCancel: this._normalizeEventForHandler(this.props.onTouchCancel),
|
||||
onTouchCancelCapture: this._normalizeEventForHandler(this.props.onTouchCancelCapture),
|
||||
onTouchEnd: this._normalizeEventForHandler(this.props.onTouchEnd),
|
||||
onTouchEndCapture: this._normalizeEventForHandler(this.props.onTouchEndCapture),
|
||||
onTouchMove: this._normalizeEventForHandler(this.props.onTouchMove),
|
||||
onTouchMoveCapture: this._normalizeEventForHandler(this.props.onTouchMoveCapture),
|
||||
onTouchStart: this._normalizeEventForHandler(this.props.onTouchStart),
|
||||
onTouchStartCapture: this._normalizeEventForHandler(this.props.onTouchStartCapture),
|
||||
...normalizedEventHandlers,
|
||||
component: this.context.isInAButtonView ? 'span' : 'div',
|
||||
style: [
|
||||
styles.initial,
|
||||
style,
|
||||
// 'View' needs to use 'flexShrink' in its reset when there is no 'flex' style provided
|
||||
(flattenedStyle.flex == null && flattenedStyle.flexShrink == null) && styles.flexReset,
|
||||
needsFlexReset && styles.flexReset,
|
||||
pointerEventsStyle
|
||||
]
|
||||
}
|
||||
|
||||
return createNativeComponent(props)
|
||||
return createReactDOMComponent(props)
|
||||
}
|
||||
|
||||
/**
|
||||
* React Native expects `pageX` and `pageY` to be on the `nativeEvent`, but
|
||||
* React doesn't include them for touch events.
|
||||
*/
|
||||
_normalizeEventForHandler(handler) {
|
||||
return (e) => {
|
||||
const { pageX } = e.nativeEvent
|
||||
if (pageX === undefined) {
|
||||
e.nativeEvent = normalizeNativeEvent(e.nativeEvent)
|
||||
}
|
||||
handler && handler(e)
|
||||
const callback = (e) => {
|
||||
e.nativeEvent = normalizeNativeEvent(e.nativeEvent)
|
||||
return handler(e)
|
||||
}
|
||||
return callback
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,7 +156,6 @@ const styles = StyleSheet.create({
|
||||
// list reset
|
||||
listStyle: 'none',
|
||||
// fix flexbox bugs
|
||||
maxWidth: '100%',
|
||||
minHeight: 0,
|
||||
minWidth: 0
|
||||
},
|
||||
@@ -129,4 +164,4 @@ const styles = StyleSheet.create({
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = View
|
||||
module.exports = applyLayout(applyNativeMethods(View))
|
||||
|
||||
12
src/index.js
12
src/index.js
@@ -1,4 +1,4 @@
|
||||
import './apis/PanResponder/injectResponderEventPlugin'
|
||||
import './modules/injectResponderEventPlugin'
|
||||
|
||||
import findNodeHandle from './modules/findNodeHandle'
|
||||
import ReactDOM from 'react-dom'
|
||||
@@ -18,12 +18,12 @@ import PixelRatio from './apis/PixelRatio'
|
||||
import Platform from './apis/Platform'
|
||||
import StyleSheet from './apis/StyleSheet'
|
||||
import UIManager from './apis/UIManager'
|
||||
import Vibration from './apis/Vibration'
|
||||
|
||||
// components
|
||||
import ActivityIndicator from './components/ActivityIndicator'
|
||||
import Image from './components/Image'
|
||||
import ListView from './components/ListView'
|
||||
import Portal from './components/Portal'
|
||||
import ScrollView from './components/ScrollView'
|
||||
import Text from './components/Text'
|
||||
import TextInput from './components/TextInput'
|
||||
@@ -39,9 +39,9 @@ import NativeModules from './modules/NativeModules'
|
||||
|
||||
// propTypes
|
||||
|
||||
import ColorPropType from './apis/StyleSheet/ColorPropType'
|
||||
import EdgeInsetsPropType from './apis/StyleSheet/EdgeInsetsPropType'
|
||||
import PointPropType from './apis/StyleSheet/PointPropType'
|
||||
import ColorPropType from './propTypes/ColorPropType'
|
||||
import EdgeInsetsPropType from './propTypes/EdgeInsetsPropType'
|
||||
import PointPropType from './propTypes/PointPropType'
|
||||
|
||||
const ReactNative = {
|
||||
// top-level API
|
||||
@@ -66,12 +66,12 @@ const ReactNative = {
|
||||
Platform,
|
||||
StyleSheet,
|
||||
UIManager,
|
||||
Vibration,
|
||||
|
||||
// components
|
||||
ActivityIndicator,
|
||||
Image,
|
||||
ListView,
|
||||
Portal,
|
||||
ScrollView,
|
||||
Text,
|
||||
TextInput,
|
||||
|
||||
@@ -113,11 +113,11 @@ const NativeMethodsMixin = {
|
||||
* In the future, we should cleanup callbacks by cancelling them instead of
|
||||
* using this.
|
||||
*/
|
||||
const mountSafeCallback = (context: Component, callback: ?Function) => () => {
|
||||
if (!callback || (context.isMounted && !context.isMounted())) {
|
||||
return
|
||||
const mountSafeCallback = (context: Component, callback: ?Function) => (...args) => {
|
||||
if (!callback) {
|
||||
return undefined
|
||||
}
|
||||
return callback.apply(context, arguments)
|
||||
return callback.apply(context, args)
|
||||
}
|
||||
|
||||
module.exports = NativeMethodsMixin
|
||||
|
||||
45
src/modules/ReactNativePropRegistry/index.js
Normal file
45
src/modules/ReactNativePropRegistry/index.js
Normal file
@@ -0,0 +1,45 @@
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNativePropRegistry
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const emptyObject = {};
|
||||
const objects = {};
|
||||
let uniqueID = 1;
|
||||
|
||||
class ReactNativePropRegistry {
|
||||
static register(object: Object): number {
|
||||
let id = ++uniqueID;
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
Object.freeze(object);
|
||||
}
|
||||
objects[id] = object;
|
||||
return id;
|
||||
}
|
||||
|
||||
static getByID(id: number): Object {
|
||||
if (!id) {
|
||||
// Used in the style={[condition && id]} pattern,
|
||||
// we want it to be a no-op when the value is false or null
|
||||
return emptyObject;
|
||||
}
|
||||
|
||||
const object = objects[id];
|
||||
if (!object) {
|
||||
console.warn('Invalid style with id `' + id + '`. Skipping ...');
|
||||
return emptyObject;
|
||||
}
|
||||
return object;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ReactNativePropRegistry;
|
||||
43
src/modules/applyLayout/index.js
Normal file
43
src/modules/applyLayout/index.js
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright (c) 2016-present, Nicolas Gallagher.
|
||||
* All rights reserved.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import emptyFunction from 'fbjs/lib/emptyFunction'
|
||||
|
||||
const applyLayout = (Component) => {
|
||||
const componentDidMount = Component.prototype.componentDidMount || emptyFunction
|
||||
const componentDidUpdate = Component.prototype.componentDidUpdate || emptyFunction
|
||||
|
||||
Component.prototype.componentDidMount = function () {
|
||||
componentDidMount()
|
||||
this._layoutState = {}
|
||||
this._handleLayout()
|
||||
}
|
||||
|
||||
Component.prototype.componentDidUpdate = function () {
|
||||
componentDidUpdate()
|
||||
this._handleLayout()
|
||||
}
|
||||
|
||||
Component.prototype._handleLayout = function () {
|
||||
const layout = this._layoutState
|
||||
const { onLayout } = this.props
|
||||
|
||||
if (onLayout) {
|
||||
this.measure((x, y, width, height) => {
|
||||
if (layout.x !== x || layout.y !== y || layout.width !== width || layout.height !== height) {
|
||||
const nextLayout = { x, y, width, height }
|
||||
const nativeEvent = { layout: nextLayout }
|
||||
onLayout({ nativeEvent })
|
||||
this._layoutState = nextLayout
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
return Component
|
||||
}
|
||||
|
||||
module.exports = applyLayout
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
import NativeMethodsMixin from '../NativeMethodsMixin'
|
||||
|
||||
const NativeMethodsDecorator = (Component) => {
|
||||
const applyNativeMethods = (Component) => {
|
||||
Object.keys(NativeMethodsMixin).forEach((method) => {
|
||||
if (!Component.prototype[method]) {
|
||||
Component.prototype[method] = NativeMethodsMixin[method]
|
||||
@@ -16,4 +16,4 @@ const NativeMethodsDecorator = (Component) => {
|
||||
return Component
|
||||
}
|
||||
|
||||
module.exports = NativeMethodsDecorator
|
||||
module.exports = applyNativeMethods
|
||||
@@ -1,61 +0,0 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
import * as utils from '../../specHelpers'
|
||||
import assert from 'assert'
|
||||
|
||||
import createNativeComponent from '../'
|
||||
|
||||
suite('modules/createNativeComponent', () => {
|
||||
test('prop "accessibilityLabel"', () => {
|
||||
const accessibilityLabel = 'accessibilityLabel'
|
||||
const dom = utils.renderToDOM(createNativeComponent({ accessibilityLabel }))
|
||||
assert.equal(dom.getAttribute('aria-label'), accessibilityLabel)
|
||||
})
|
||||
|
||||
test('prop "accessibilityLiveRegion"', () => {
|
||||
const accessibilityLiveRegion = 'polite'
|
||||
const dom = utils.renderToDOM(createNativeComponent({ accessibilityLiveRegion }))
|
||||
assert.equal(dom.getAttribute('aria-live'), accessibilityLiveRegion)
|
||||
})
|
||||
|
||||
test('prop "accessibilityRole"', () => {
|
||||
const accessibilityRole = 'banner'
|
||||
let dom = utils.renderToDOM(createNativeComponent({ accessibilityRole }))
|
||||
assert.equal(dom.getAttribute('role'), accessibilityRole)
|
||||
assert.equal((dom.tagName).toLowerCase(), 'header')
|
||||
|
||||
const button = 'button'
|
||||
dom = utils.renderToDOM(createNativeComponent({ accessibilityRole: 'button' }))
|
||||
assert.equal(dom.getAttribute('type'), button)
|
||||
assert.equal((dom.tagName).toLowerCase(), button)
|
||||
})
|
||||
|
||||
test('prop "accessible"', () => {
|
||||
// accessible (implicit)
|
||||
let dom = utils.renderToDOM(createNativeComponent({}))
|
||||
assert.equal(dom.getAttribute('aria-hidden'), null)
|
||||
// accessible (explicit)
|
||||
dom = utils.renderToDOM(createNativeComponent({ accessible: true }))
|
||||
assert.equal(dom.getAttribute('aria-hidden'), null)
|
||||
// not accessible
|
||||
dom = utils.renderToDOM(createNativeComponent({ accessible: false }))
|
||||
assert.equal(dom.getAttribute('aria-hidden'), 'true')
|
||||
})
|
||||
|
||||
test('prop "component"', () => {
|
||||
const component = 'main'
|
||||
const dom = utils.renderToDOM(createNativeComponent({ component }))
|
||||
const tagName = (dom.tagName).toLowerCase()
|
||||
assert.equal(tagName, component)
|
||||
})
|
||||
|
||||
test('prop "testID"', () => {
|
||||
// no testID
|
||||
let dom = utils.renderToDOM(createNativeComponent({}))
|
||||
assert.equal(dom.getAttribute('data-testid'), null)
|
||||
// with testID
|
||||
const testID = 'Example.testID'
|
||||
dom = utils.renderToDOM(createNativeComponent({ testID }))
|
||||
assert.equal(dom.getAttribute('data-testid'), testID)
|
||||
})
|
||||
})
|
||||
61
src/modules/createReactDOMComponent/__tests__/index-test.js
Normal file
61
src/modules/createReactDOMComponent/__tests__/index-test.js
Normal file
@@ -0,0 +1,61 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
import assert from 'assert'
|
||||
import createReactDOMComponent from '..'
|
||||
import { shallow } from 'enzyme'
|
||||
|
||||
suite('modules/createReactDOMComponent', () => {
|
||||
test('prop "accessibilityLabel"', () => {
|
||||
const accessibilityLabel = 'accessibilityLabel'
|
||||
const element = shallow(createReactDOMComponent({ accessibilityLabel }))
|
||||
assert.equal(element.prop('aria-label'), accessibilityLabel)
|
||||
})
|
||||
|
||||
test('prop "accessibilityLiveRegion"', () => {
|
||||
const accessibilityLiveRegion = 'polite'
|
||||
const element = shallow(createReactDOMComponent({ accessibilityLiveRegion }))
|
||||
assert.equal(element.prop('aria-live'), accessibilityLiveRegion)
|
||||
})
|
||||
|
||||
test('prop "accessibilityRole"', () => {
|
||||
const accessibilityRole = 'banner'
|
||||
let element = shallow(createReactDOMComponent({ accessibilityRole }))
|
||||
assert.equal(element.prop('role'), accessibilityRole)
|
||||
assert.equal(element.is('header'), true)
|
||||
|
||||
const button = 'button'
|
||||
element = shallow(createReactDOMComponent({ accessibilityRole: 'button' }))
|
||||
assert.equal(element.prop('type'), button)
|
||||
assert.equal(element.is('button'), true)
|
||||
})
|
||||
|
||||
test('prop "accessible"', () => {
|
||||
// accessible (implicit)
|
||||
let element = shallow(createReactDOMComponent({}))
|
||||
assert.equal(element.prop('aria-hidden'), null)
|
||||
// accessible (explicit)
|
||||
element = shallow(createReactDOMComponent({ accessible: true }))
|
||||
assert.equal(element.prop('aria-hidden'), null)
|
||||
// not accessible
|
||||
element = shallow(createReactDOMComponent({ accessible: false }))
|
||||
assert.equal(element.prop('aria-hidden'), true)
|
||||
})
|
||||
|
||||
test('prop "component"', () => {
|
||||
const component = 'main'
|
||||
let element = shallow(createReactDOMComponent({}))
|
||||
assert.equal(element.is('span'), true, 'Default element must be a "span"')
|
||||
element = shallow(createReactDOMComponent({ component }))
|
||||
assert.equal(element.is('main'), true)
|
||||
})
|
||||
|
||||
test('prop "testID"', () => {
|
||||
// no testID
|
||||
let element = shallow(createReactDOMComponent({}))
|
||||
assert.equal(element.prop('data-testid'), null)
|
||||
// with testID
|
||||
const testID = 'Example.testID'
|
||||
element = shallow(createReactDOMComponent({ testID }))
|
||||
assert.equal(element.prop('data-testid'), testID)
|
||||
})
|
||||
})
|
||||
@@ -17,17 +17,18 @@ const roleComponents = {
|
||||
region: 'section'
|
||||
}
|
||||
|
||||
const createNativeComponent = ({
|
||||
const createReactDOMComponent = ({
|
||||
accessibilityLabel,
|
||||
accessibilityLiveRegion,
|
||||
accessibilityRole,
|
||||
accessible = true,
|
||||
component = 'div',
|
||||
component = 'span',
|
||||
testID,
|
||||
type,
|
||||
...other
|
||||
}) => {
|
||||
const Component = accessibilityRole && roleComponents[accessibilityRole] ? roleComponents[accessibilityRole] : component
|
||||
const role = accessibilityRole
|
||||
const Component = role && roleComponents[role] ? roleComponents[role] : component
|
||||
|
||||
return (
|
||||
<Component
|
||||
@@ -37,13 +38,13 @@ const createNativeComponent = ({
|
||||
aria-label={accessibilityLabel}
|
||||
aria-live={accessibilityLiveRegion}
|
||||
data-testid={testID}
|
||||
role={accessibilityRole}
|
||||
type={accessibilityRole === 'button' ? 'button' : type}
|
||||
role={role}
|
||||
type={role === 'button' ? 'button' : type}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
createNativeComponent.propTypes = {
|
||||
createReactDOMComponent.propTypes = {
|
||||
accessibilityLabel: PropTypes.string,
|
||||
accessibilityLiveRegion: PropTypes.oneOf([ 'assertive', 'off', 'polite' ]),
|
||||
accessibilityRole: PropTypes.string,
|
||||
@@ -54,4 +55,4 @@ createNativeComponent.propTypes = {
|
||||
type: PropTypes.string
|
||||
}
|
||||
|
||||
module.exports = createNativeComponent
|
||||
module.exports = createReactDOMComponent
|
||||
@@ -10,9 +10,9 @@
|
||||
*/
|
||||
|
||||
import assert from 'assert'
|
||||
import flattenStyle from '../flattenStyle'
|
||||
import flattenStyle from '..'
|
||||
|
||||
suite('apis/StyleSheet/flattenStyle', () => {
|
||||
suite('modules/flattenStyle', () => {
|
||||
test('should merge style objects', () => {
|
||||
const style1 = {opacity: 1}
|
||||
const style2 = {order: 2}
|
||||
47
src/modules/flattenStyle/index.js
Normal file
47
src/modules/flattenStyle/index.js
Normal file
@@ -0,0 +1,47 @@
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule flattenStyle
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var ReactNativePropRegistry = require('../ReactNativePropRegistry');
|
||||
var invariant = require('fbjs/lib/invariant');
|
||||
|
||||
function getStyle(style) {
|
||||
if (typeof style === 'number') {
|
||||
return ReactNativePropRegistry.getByID(style);
|
||||
}
|
||||
return style;
|
||||
}
|
||||
|
||||
function flattenStyle(style: ?StyleObj): ?Object {
|
||||
if (!style) {
|
||||
return undefined;
|
||||
}
|
||||
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]);
|
||||
if (computedStyle) {
|
||||
for (var key in computedStyle) {
|
||||
result[key] = computedStyle[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
module.exports = flattenStyle;
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import EventConstants from 'react/lib/EventConstants'
|
||||
import EventPluginRegistry from 'react/lib/EventPluginRegistry'
|
||||
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment'
|
||||
import ResponderEventPlugin from 'react/lib/ResponderEventPlugin'
|
||||
import ResponderTouchHistoryStore from 'react/lib/ResponderTouchHistoryStore'
|
||||
import normalizeNativeEvent from './normalizeNativeEvent'
|
||||
@@ -19,14 +18,9 @@ const {
|
||||
topTouchStart
|
||||
} = EventConstants.topLevelTypes
|
||||
|
||||
const supportsTouch = ExecutionEnvironment.canUseDOM && (
|
||||
'ontouchstart' in window ||
|
||||
window.DocumentTouch && document instanceof window.DocumentTouch
|
||||
)
|
||||
|
||||
const endDependencies = supportsTouch ? [ topTouchCancel, topTouchEnd ] : [ topMouseUp ]
|
||||
const moveDependencies = supportsTouch ? [ topTouchMove ] : [ topMouseMove ]
|
||||
const startDependencies = supportsTouch ? [ topTouchStart ] : [ topMouseDown ]
|
||||
const endDependencies = [ topTouchCancel, topTouchEnd, topMouseUp ]
|
||||
const moveDependencies = [ topTouchMove, topMouseMove ]
|
||||
const startDependencies = [ topTouchStart, topMouseDown ]
|
||||
|
||||
/**
|
||||
* Setup ResponderEventPlugin dependencies
|
||||
@@ -51,7 +45,15 @@ ResponderTouchHistoryStore.recordTouchTrack = (topLevelType, nativeEvent) => {
|
||||
if ((topLevelType === topMouseMove) && !ResponderTouchHistoryStore.touchHistory.touchBank.length) {
|
||||
return
|
||||
}
|
||||
originalRecordTouchTrack.call(ResponderTouchHistoryStore, topLevelType, normalizeNativeEvent(nativeEvent))
|
||||
// Cancel mouse events that browsers fire after touch events
|
||||
if (topLevelType === topTouchStart || topLevelType === topTouchMove || topLevelType === topTouchEnd) {
|
||||
if (nativeEvent.target.getAttribute('href') !== undefined) {
|
||||
nativeEvent.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
const normalizedEvent = normalizeNativeEvent(nativeEvent)
|
||||
originalRecordTouchTrack.call(ResponderTouchHistoryStore, topLevelType, normalizedEvent)
|
||||
}
|
||||
|
||||
EventPluginRegistry.injectEventPluginsByName({
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user