mirror of
https://github.com/zhigang1992/react-native-web.git
synced 2026-03-31 18:21:38 +08:00
Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7cda89c5ce | ||
|
|
695eba45af | ||
|
|
92a2cb274a | ||
|
|
b1ca04d11e | ||
|
|
22ab70ea6f | ||
|
|
49f36d8eb1 | ||
|
|
80ba119b83 | ||
|
|
c30b67f6db | ||
|
|
4580f93199 | ||
|
|
4c46126ffe | ||
|
|
f8d5c15405 | ||
|
|
dc54e03625 | ||
|
|
4d5819ae28 | ||
|
|
5c482ef3be | ||
|
|
f51592f96e | ||
|
|
6bffe1775f | ||
|
|
7e75d037f2 | ||
|
|
7536508fe3 | ||
|
|
945fff0015 | ||
|
|
5032ed6fe1 | ||
|
|
8c7cdbf4be | ||
|
|
e5d8857bcc | ||
|
|
cda8d05bb7 | ||
|
|
049edc4611 | ||
|
|
e76d5a4e6c | ||
|
|
f71dae7d93 | ||
|
|
94d31beaf4 | ||
|
|
f5f9389728 | ||
|
|
fdbd19a4af | ||
|
|
36eafbc2f5 | ||
|
|
bca3398c1c | ||
|
|
722d77e8e5 | ||
|
|
d65c92eea9 | ||
|
|
8dd39c681c | ||
|
|
0b1759363d | ||
|
|
abd1227a94 | ||
|
|
8352c7cbda | ||
|
|
89f5a13891 |
3
.babelrc
3
.babelrc
@@ -1,5 +1,8 @@
|
||||
{
|
||||
"presets": [
|
||||
"react-native"
|
||||
],
|
||||
"plugins": [
|
||||
[ "transform-react-remove-prop-types", { "mode": "wrap" } ]
|
||||
]
|
||||
}
|
||||
|
||||
@@ -5,5 +5,4 @@ before_script:
|
||||
- export DISPLAY=:99.0
|
||||
- sh -e /etc/init.d/xvfb start
|
||||
script:
|
||||
- npm run lint
|
||||
- npm test
|
||||
|
||||
@@ -19,6 +19,12 @@ Fork, then clone the repo:
|
||||
git clone https://github.com/your-username/react-native-web.git
|
||||
```
|
||||
|
||||
Install dependencies (requires [yarn](https://yarnpkg.com/en/docs/install):
|
||||
|
||||
```
|
||||
yarn
|
||||
```
|
||||
|
||||
Run the examples:
|
||||
|
||||
```
|
||||
@@ -51,7 +57,7 @@ To continuously watch and run tests, run the following:
|
||||
npm run test:watch
|
||||
```
|
||||
|
||||
To perform linting, run the following:
|
||||
To perform only linting, run the following:
|
||||
|
||||
```
|
||||
npm run lint
|
||||
|
||||
@@ -27,7 +27,7 @@ online with [React Native for Web: Playground](http://codepen.io/necolas/pen/PZz
|
||||
To install in your app:
|
||||
|
||||
```
|
||||
npm install --save react react-native-web
|
||||
npm install --save react@15.3 react-native-web
|
||||
```
|
||||
|
||||
Read the [Client and Server rendering](docs/guides/rendering.md) guide.
|
||||
@@ -53,6 +53,7 @@ Exported modules:
|
||||
|
||||
* Components
|
||||
* [`ActivityIndicator`](docs/components/ActivityIndicator.md)
|
||||
* [`Button`](docs/components/Button.md)
|
||||
* [`Image`](docs/components/Image.md)
|
||||
* [`ListView`](docs/components/ListView.md)
|
||||
* [`ProgressBar`](docs/components/ProgressBar.md)
|
||||
@@ -69,6 +70,7 @@ Exported modules:
|
||||
* [`AppRegistry`](docs/apis/AppRegistry.md)
|
||||
* [`AppState`](docs/apis/AppState.md)
|
||||
* [`AsyncStorage`](docs/apis/AsyncStorage.md)
|
||||
* [`Clipboard`](docs/apis/Clipboard.md)
|
||||
* [`Dimensions`](docs/apis/Dimensions.md)
|
||||
* [`I18nManager`](docs/apis/I18nManager.md)
|
||||
* [`NativeMethods`](docs/apis/NativeMethods.md)
|
||||
@@ -142,6 +144,7 @@ AppRegistry.runApplication('MyApp', { rootTag: document.getElementById('react-ro
|
||||
* [react-native-web-player](https://github.com/dabbott/react-native-web-player)
|
||||
* [react-web](https://github.com/taobaofed/react-web)
|
||||
* [react-native-for-web](https://github.com/KodersLab/react-native-for-web)
|
||||
* [rhinos-app](https://github.com/rhinos-app/rhinos-app-dev)
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
`AppRegistry` is the control point for registering, running, prerendering, and
|
||||
unmounting all apps. App root components should register themselves with
|
||||
`AppRegistry.registerComponent`. Apps can be run by invoking
|
||||
`AppRegistry.runApplication`, and prerendered by invoking
|
||||
`AppRegistry.prerenderApplication` (see the [client and server rendering
|
||||
`AppRegistry.runApplication` (see the [client and server rendering
|
||||
guide](../guides/rendering.md) for more details).
|
||||
|
||||
To "stop" an application when a view should be destroyed, call
|
||||
@@ -13,14 +12,11 @@ into `runApplication`. These should always be used as a pair.
|
||||
|
||||
## Methods
|
||||
|
||||
(web) static **prerenderApplication**(appKey:string, appParameters: object)
|
||||
(web) static **getApplication**(appKey:string, appParameters: object)
|
||||
|
||||
Renders the given application to an HTML string. Use this for server-side
|
||||
rendering. Return object is of type `{ html: string; style: string;
|
||||
styleElement: ReactComponent }`. `html` is the prerendered HTML, `style` is the
|
||||
prerendered style sheet, and `styleElement` is a React Component. It's
|
||||
recommended that you use `styleElement` to render the style sheet in an app
|
||||
shell.
|
||||
Returns the given application element. Use this for server-side rendering.
|
||||
Return object is of type `{ element: ReactElement; stylesheet: ReactElement }`.
|
||||
It's recommended that you use `sheetsheet` to render the style sheet in an app
|
||||
|
||||
static **registerConfig**(config: Array<AppConfig>)
|
||||
|
||||
|
||||
16
docs/apis/Clipboard.md
Normal file
16
docs/apis/Clipboard.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# Clipboard
|
||||
|
||||
Clipboard gives you an interface for setting to the clipboard. (Getting
|
||||
clipboard content is not supported on web.)
|
||||
|
||||
## Methods
|
||||
|
||||
static **getString**()
|
||||
|
||||
Returns a `Promise` of an empty string.
|
||||
|
||||
static **setString**(content: string): boolean
|
||||
|
||||
Copies a string to the clipboard. On web, some browsers may not support copying
|
||||
to the clipboard, therefore, this function returns a boolean to indicate if the
|
||||
copy was successful.
|
||||
39
docs/components/Button.md
Normal file
39
docs/components/Button.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Button
|
||||
|
||||
A basic button component. Supports a minimal level of customization. You can
|
||||
build your own custom button using `TouchableOpacity` or
|
||||
`TouchableNativeFeedback`.
|
||||
|
||||
## Props
|
||||
|
||||
**accessibilityLabel**: string
|
||||
|
||||
Defines the text available to assistive technologies upon interaction with the
|
||||
element. (This is implemented using `aria-label`.)
|
||||
|
||||
**color**: string
|
||||
|
||||
Background color of the button.
|
||||
|
||||
**disabled**: bool = false
|
||||
|
||||
If true, disable all interactions for this component
|
||||
|
||||
**onPress**: function
|
||||
|
||||
This function is called on press.
|
||||
|
||||
**title**: string
|
||||
|
||||
Text to display inside the button.
|
||||
|
||||
## Examples
|
||||
|
||||
```js
|
||||
<Button
|
||||
accessibilityLabel="Learn more about this purple button"
|
||||
color="#841584"
|
||||
onPress={onPressLearnMore}
|
||||
title="Learn More"
|
||||
/>
|
||||
```
|
||||
@@ -38,6 +38,18 @@ which this `ScrollView` renders.
|
||||
Fires at most once per frame during scrolling. The frequency of the events can
|
||||
be contolled using the `scrollEventThrottle` prop.
|
||||
|
||||
Invoked on scroll with the following event:
|
||||
|
||||
```js
|
||||
{
|
||||
nativeEvent: {
|
||||
contentOffset: { x, y },
|
||||
contentSize: { height, width },
|
||||
layoutMeasurement: { height, width }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**refreshControl**: element
|
||||
|
||||
TODO
|
||||
@@ -51,8 +63,8 @@ When false, the content does not scroll.
|
||||
|
||||
**scrollEventThrottle**: number = 0
|
||||
|
||||
This controls how often the scroll event will be fired while scrolling (in
|
||||
events per seconds). A higher number yields better accuracy for code that is
|
||||
This controls how often the scroll event will be fired while scrolling (as a
|
||||
time interval in ms). A lower number yields better accuracy for code that is
|
||||
tracking the scroll position, but can lead to scroll performance problems. The
|
||||
default value is `0`, which means the scroll event will be sent only once each
|
||||
time the view is scrolled.
|
||||
@@ -104,7 +116,7 @@ export default class ScrollViewExample extends Component {
|
||||
contentContainerStyle={styles.container}
|
||||
horizontal
|
||||
onScroll={(e) => this.onScroll(e)}
|
||||
scrollEventThrottle={60}
|
||||
scrollEventThrottle={100}
|
||||
style={styles.root}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -6,12 +6,10 @@ such as auto-complete, auto-focus, placeholder text, and event callbacks.
|
||||
Note: some props are exclusive to or excluded from `multiline`.
|
||||
|
||||
Unsupported React Native props:
|
||||
`autoCapitalize`,
|
||||
`autoCorrect`,
|
||||
`onEndEditing`,
|
||||
`onSubmitEditing`,
|
||||
`clearButtonMode` (ios),
|
||||
`enablesReturnKeyAutomatically` (ios),
|
||||
`placeholderTextColor`,
|
||||
`returnKeyType` (ios),
|
||||
`selectionState` (ios),
|
||||
`underlineColorAndroid` (android)
|
||||
@@ -128,10 +126,6 @@ Callback that is called when the keyboard's submit button is pressed.
|
||||
The string that will be rendered in an empty `TextInput` before text has been
|
||||
entered.
|
||||
|
||||
**placeholderTextColor**: string
|
||||
|
||||
The text color of the placeholder string.
|
||||
|
||||
**secureTextEntry**: bool = false
|
||||
|
||||
If true, the text input obscures the text entered so that sensitive text like
|
||||
|
||||
@@ -43,12 +43,7 @@ import ReactNative from 'react-native'
|
||||
// component that renders the app
|
||||
const AppHeaderContainer = (props) => { /* ... */ }
|
||||
|
||||
// DOM render
|
||||
ReactNative.render(<AppHeaderContainer />, document.getElementById('react-app-header'))
|
||||
|
||||
// Server render
|
||||
ReactNative.renderToString(<AppHeaderContainer />)
|
||||
ReactNative.renderToStaticMarkup(<AppHeaderContainer />)
|
||||
```
|
||||
|
||||
Rendering using the `AppRegistry`:
|
||||
@@ -63,12 +58,27 @@ const AppContainer = (props) => { /* ... */ }
|
||||
// register the app
|
||||
AppRegistry.registerComponent('App', () => AppContainer)
|
||||
|
||||
// DOM render
|
||||
AppRegistry.runApplication('App', {
|
||||
initialProps: {},
|
||||
rootTag: document.getElementById('react-app')
|
||||
})
|
||||
```
|
||||
|
||||
## Server-side rendering
|
||||
|
||||
Rendering using the `AppRegistry`:
|
||||
|
||||
```js
|
||||
import ReactDOMServer from 'react-dom/server'
|
||||
import ReactNative, { AppRegistry } from 'react-native'
|
||||
|
||||
// component that renders the app
|
||||
const AppContainer = (props) => { /* ... */ }
|
||||
|
||||
// register the app
|
||||
AppRegistry.registerComponent('App', () => AppContainer)
|
||||
|
||||
// prerender the app
|
||||
const { html, styleElement } = AppRegistry.prerenderApplication('App', { initialProps })
|
||||
const { element, stylesheet } = AppRegistry.getApplication('App', { initialProps });
|
||||
const initialHTML = ReactDOMServer.renderToString(element);
|
||||
```
|
||||
|
||||
33
examples/apis/Clipboard/ClipboardExample.js
Normal file
33
examples/apis/Clipboard/ClipboardExample.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import { Clipboard, Text, TextInput, View } from 'react-native'
|
||||
import React, { Component } from 'react';
|
||||
import { action, storiesOf } from '@kadira/storybook';
|
||||
|
||||
class ClipboardExample extends Component {
|
||||
render() {
|
||||
return (
|
||||
<View style={{ minWidth: 300 }}>
|
||||
<Text onPress={this._handleSet}>Copy to clipboard</Text>
|
||||
<TextInput
|
||||
multiline={true}
|
||||
placeholder={'Try pasting here afterwards'}
|
||||
style={{ borderWidth: 1, height: 200, marginVertical: 20 }}
|
||||
/>
|
||||
<Text onPress={this._handleGet}>(Clipboard.getString returns a Promise that always resolves to an empty string on web)</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
_handleGet() {
|
||||
Clipboard.getString().then((value) => { console.log(`Clipboard value: ${value}`) });
|
||||
}
|
||||
|
||||
_handleSet() {
|
||||
const success = Clipboard.setString('This text was copied to the clipboard by React Native');
|
||||
console.log(`Clipboard.setString success? ${success}`);
|
||||
}
|
||||
}
|
||||
|
||||
storiesOf('api: Clipboard', module)
|
||||
.add('setString', () => (
|
||||
<ClipboardExample />
|
||||
));
|
||||
@@ -1,8 +1,8 @@
|
||||
import { storiesOf } from '@kadira/storybook';
|
||||
import { I18nManager, StyleSheet, TouchableHighlight, Text, View } from 'react-native'
|
||||
import React, { Component } from 'react';
|
||||
import { storiesOf, action } from '@kadira/storybook';
|
||||
|
||||
class RTLExample extends Component {
|
||||
class I18nManagerExample extends Component {
|
||||
componentWillUnmount() {
|
||||
I18nManager.setPreferredLanguageRTL(false)
|
||||
}
|
||||
@@ -75,5 +75,5 @@ const styles = StyleSheet.create({
|
||||
|
||||
storiesOf('api: I18nManager', module)
|
||||
.add('RTL layout', () => (
|
||||
<RTLExample />
|
||||
<I18nManagerExample />
|
||||
))
|
||||
29
examples/apis/Linking/LinkingExample.js
Normal file
29
examples/apis/Linking/LinkingExample.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Linking, StyleSheet, Text, View } from 'react-native'
|
||||
import React, { Component } from 'react';
|
||||
import { storiesOf, action } from '@kadira/storybook';
|
||||
|
||||
class LinkingExample extends Component {
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<Text onPress={() => { Linking.openURL('https://mathiasbynens.github.io/rel-noopener/malicious.html'); }} style={styles.text}>
|
||||
Linking.openURL (Expect: "The previous tab is safe and intact")
|
||||
</Text>
|
||||
<Text accessibilityRole='link' href='https://mathiasbynens.github.io/rel-noopener/malicious.html' style={styles.text} target='_blank'>
|
||||
target="_blank" (Expect: "The previous tab is safe and intact")
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
text: {
|
||||
marginVertical: 10
|
||||
}
|
||||
});
|
||||
|
||||
storiesOf('api: Linking', module)
|
||||
.add('Safe linking', () => (
|
||||
<LinkingExample />
|
||||
));
|
||||
80
examples/components/Button/ButtonExample.js
Normal file
80
examples/components/Button/ButtonExample.js
Normal file
@@ -0,0 +1,80 @@
|
||||
import React from 'react';
|
||||
import { action, storiesOf } from '@kadira/storybook';
|
||||
import { Button, StyleSheet, View } from 'react-native';
|
||||
|
||||
const onButtonPress = action('Button has been pressed!');
|
||||
|
||||
const examples = [
|
||||
{
|
||||
title: 'Simple Button',
|
||||
description: 'The title and onPress handler are required. It is ' +
|
||||
'recommended to set accessibilityLabel to help make your app usable by ' +
|
||||
'everyone.',
|
||||
render: function() {
|
||||
return (
|
||||
<Button
|
||||
onPress={onButtonPress}
|
||||
title="Press Me"
|
||||
accessibilityLabel="See an informative alert"
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Adjusted color',
|
||||
description: 'Adjusts the color in a way that looks standard on each ' +
|
||||
'platform. On iOS, the color prop controls the color of the text. On ' +
|
||||
'Android, the color adjusts the background color of the button.',
|
||||
render: function() {
|
||||
return (
|
||||
<Button
|
||||
onPress={onButtonPress}
|
||||
title="Press Purple"
|
||||
color="#841584"
|
||||
accessibilityLabel="Learn more about purple"
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Fit to text layout',
|
||||
description: 'This layout strategy lets the title define the width of ' +
|
||||
'the button',
|
||||
render: function() {
|
||||
return (
|
||||
<View style={{flexDirection: 'row', justifyContent: 'space-between'}}>
|
||||
<Button
|
||||
onPress={onButtonPress}
|
||||
title="This looks great!"
|
||||
accessibilityLabel="This sounds great!"
|
||||
/>
|
||||
<Button
|
||||
onPress={onButtonPress}
|
||||
title="Ok!"
|
||||
color="#841584"
|
||||
accessibilityLabel="Ok, Great!"
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Disabled Button',
|
||||
description: 'All interactions for the component are disabled.',
|
||||
render: function() {
|
||||
return (
|
||||
<Button
|
||||
disabled
|
||||
onPress={onButtonPress}
|
||||
title="I Am Disabled"
|
||||
accessibilityLabel="See an informative alert"
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
examples.forEach((example) => {
|
||||
storiesOf('component: Button', module)
|
||||
.add(example.title, () => example.render());
|
||||
});
|
||||
@@ -1,4 +1,80 @@
|
||||
import React from 'react';
|
||||
import { storiesOf, action } from '@kadira/storybook';
|
||||
import { ListView } from 'react-native'
|
||||
import { storiesOf } from '@kadira/storybook';
|
||||
import { ListView, StyleSheet, Text, View } from 'react-native';
|
||||
|
||||
const generateData = (length) => Array.from({ length }).map((item, i) => i);
|
||||
const dataSource = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
|
||||
|
||||
storiesOf('component: ListView', module)
|
||||
.add('vertical', () => (
|
||||
<View style={styles.scrollViewContainer}>
|
||||
<ListView
|
||||
contentContainerStyle={styles.scrollViewContentContainerStyle}
|
||||
dataSource={dataSource.cloneWithRows(generateData(100))}
|
||||
initialListSize={100}
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onScroll={(e) => { console.log('ScrollView.onScroll', e); } }
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
renderRow={(row) => (
|
||||
<View><Text>{row}</Text></View>
|
||||
)}
|
||||
scrollEventThrottle={1000} // 1 event per second
|
||||
style={styles.scrollViewStyle}
|
||||
/>
|
||||
</View>
|
||||
))
|
||||
.add('incremental rendering - large pageSize', () => (
|
||||
<View style={styles.scrollViewContainer}>
|
||||
<ListView
|
||||
contentContainerStyle={styles.scrollViewContentContainerStyle}
|
||||
dataSource={dataSource.cloneWithRows(generateData(5000))}
|
||||
initialListSize={100}
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onScroll={(e) => { console.log('ScrollView.onScroll', e); } }
|
||||
pageSize={50}
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
renderRow={(row) => (
|
||||
<View><Text>{row}</Text></View>
|
||||
)}
|
||||
scrollEventThrottle={1000} // 1 event per second
|
||||
style={styles.scrollViewStyle}
|
||||
/>
|
||||
</View>
|
||||
))
|
||||
.add('incremental rendering - small pageSize', () => (
|
||||
<View style={styles.scrollViewContainer}>
|
||||
<ListView
|
||||
contentContainerStyle={styles.scrollViewContentContainerStyle}
|
||||
dataSource={dataSource.cloneWithRows(generateData(5000))}
|
||||
initialListSize={5}
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onScroll={(e) => { console.log('ScrollView.onScroll', e); } }
|
||||
pageSize={1}
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
renderRow={(row) => (
|
||||
<View><Text>{row}</Text></View>
|
||||
)}
|
||||
scrollEventThrottle={1000} // 1 event per second
|
||||
style={styles.scrollViewStyle}
|
||||
/>
|
||||
</View>
|
||||
));
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
box: {
|
||||
flexGrow: 1,
|
||||
justifyContent: 'center',
|
||||
borderWidth: 1
|
||||
},
|
||||
scrollViewContainer: {
|
||||
height: '200px',
|
||||
width: 300
|
||||
},
|
||||
scrollViewStyle: {
|
||||
borderWidth: '1px'
|
||||
},
|
||||
scrollViewContentContainerStyle: {
|
||||
backgroundColor: '#eee',
|
||||
padding: '10px'
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import React from 'react';
|
||||
import { storiesOf, action } from '@kadira/storybook';
|
||||
import { action, storiesOf } from '@kadira/storybook';
|
||||
import { ScrollView, StyleSheet, Text, TouchableHighlight, View } from 'react-native'
|
||||
|
||||
const onScroll = action('ScrollView.onScroll');
|
||||
|
||||
storiesOf('component: ScrollView', module)
|
||||
.add('vertical', () => (
|
||||
<View style={styles.scrollViewContainer}>
|
||||
<ScrollView
|
||||
contentContainerStyle={styles.scrollViewContentContainerStyle}
|
||||
onScroll={e => console.log('ScrollView.onScroll', e)}
|
||||
scrollEventThrottle={1} // 1 event per second
|
||||
onScroll={onScroll}
|
||||
scrollEventThrottle={1000} // 1 event per second
|
||||
style={styles.scrollViewStyle}
|
||||
>
|
||||
{Array.from({ length: 50 }).map((item, i) => (
|
||||
@@ -24,8 +26,8 @@ storiesOf('component: ScrollView', module)
|
||||
<ScrollView
|
||||
contentContainerStyle={styles.scrollViewContentContainerStyle}
|
||||
horizontal
|
||||
onScroll={e => console.log('ScrollView.onScroll', e)}
|
||||
scrollEventThrottle={1} // 1 event per second
|
||||
onScroll={onScroll}
|
||||
scrollEventThrottle={16} // ~60 events per second
|
||||
style={styles.scrollViewStyle}
|
||||
>
|
||||
{Array.from({ length: 50 }).map((item, i) => (
|
||||
@@ -48,10 +50,10 @@ const styles = StyleSheet.create({
|
||||
width: 300
|
||||
},
|
||||
scrollViewStyle: {
|
||||
borderWidth: '1px'
|
||||
borderWidth: 1
|
||||
},
|
||||
scrollViewContentContainerStyle: {
|
||||
backgroundColor: '#eee',
|
||||
padding: '10px'
|
||||
padding: 10
|
||||
}
|
||||
})
|
||||
|
||||
@@ -79,7 +79,7 @@ const examples = [
|
||||
title: 'Wrap',
|
||||
render: function() {
|
||||
return (
|
||||
<Text>
|
||||
<Text style={{ WebkitFontSmoothing: 'antialiased' }}>
|
||||
The text should wrap if it goes on multiple lines. See, this is going to
|
||||
the next line.
|
||||
</Text>
|
||||
|
||||
@@ -59,7 +59,7 @@ class TextEventsExample extends React.Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<View style={{ alignItems: 'center' }}>
|
||||
<TextInput
|
||||
autoCapitalize="none"
|
||||
placeholder="Enter text to see events"
|
||||
@@ -83,7 +83,7 @@ class TextEventsExample extends React.Component {
|
||||
onKeyPress={(event) => {
|
||||
this.updateText('onKeyPress key: ' + event.nativeEvent.key);
|
||||
}}
|
||||
style={styles.default}
|
||||
style={[ styles.default, { maxWidth: 200 } ]}
|
||||
/>
|
||||
<Text style={styles.eventLabel}>
|
||||
{this.state.curText}{'\n'}
|
||||
@@ -210,25 +210,16 @@ class TokenizedTextExample extends React.Component {
|
||||
}
|
||||
parts.push(_text);
|
||||
|
||||
//highlight hashtags
|
||||
parts = parts.map((text) => {
|
||||
if (/^#/.test(text)) {
|
||||
return <Text key={text} style={styles.hashtag}>{text}</Text>;
|
||||
} else {
|
||||
return text;
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<View>
|
||||
<TextInput
|
||||
value={parts.join('')}
|
||||
multiline={true}
|
||||
style={styles.multiline}
|
||||
onChangeText={(text) => {
|
||||
this.setState({text});
|
||||
}}>
|
||||
<Text>{parts}</Text>
|
||||
</TextInput>
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -279,7 +270,7 @@ class BlurOnSubmitExample extends React.Component {
|
||||
<TextInput
|
||||
ref="5"
|
||||
style={styles.default}
|
||||
keyboardType="numbers-and-punctuation"
|
||||
keyboardType="numeric"
|
||||
placeholder="blurOnSubmit = true"
|
||||
returnKeyType="done"
|
||||
/>
|
||||
@@ -519,15 +510,15 @@ const examples = [
|
||||
render: function() {
|
||||
var keyboardTypes = [
|
||||
'default',
|
||||
'ascii-capable',
|
||||
'numbers-and-punctuation',
|
||||
//'ascii-capable',
|
||||
//'numbers-and-punctuation',
|
||||
'url',
|
||||
'number-pad',
|
||||
'phone-pad',
|
||||
'name-phone-pad',
|
||||
//'name-phone-pad',
|
||||
'email-address',
|
||||
'decimal-pad',
|
||||
'twitter',
|
||||
//'decimal-pad',
|
||||
//'twitter',
|
||||
'web-search',
|
||||
'numeric',
|
||||
];
|
||||
@@ -776,14 +767,6 @@ const examples = [
|
||||
style={styles.multiline}
|
||||
dataDetectorTypes="phoneNumber"
|
||||
/>
|
||||
<TextInput
|
||||
placeholder="multiline with children"
|
||||
multiline={true}
|
||||
enablesReturnKeyAutomatically={true}
|
||||
returnKeyType="go"
|
||||
style={styles.multiline}>
|
||||
<View style={styles.multilineChild}/>
|
||||
</TextInput>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ var {
|
||||
AppRegistry,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableBounce,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} = ReactNative;
|
||||
|
||||
@@ -139,9 +139,9 @@ class GameEndOverlay extends React.Component {
|
||||
return (
|
||||
<View style={styles.overlay}>
|
||||
<Text style={styles.overlayMessage}>{message}</Text>
|
||||
<TouchableBounce onPress={this.props.onRestart} style={styles.tryAgain}>
|
||||
<TouchableOpacity onPress={this.props.onRestart} style={styles.tryAgain}>
|
||||
<Text style={styles.tryAgainText}>Try Again?</Text>
|
||||
</TouchableBounce>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
19
package.json
19
package.json
@@ -1,10 +1,12 @@
|
||||
{
|
||||
"name": "react-native-web",
|
||||
"version": "0.0.51",
|
||||
"version": "0.0.60",
|
||||
"description": "React Native for Web",
|
||||
"main": "dist/index.js",
|
||||
"files": [
|
||||
"dist"
|
||||
"dist",
|
||||
"src",
|
||||
"!**/__tests__"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "del ./dist && mkdir dist && babel src -d dist --ignore **/__tests__",
|
||||
@@ -14,15 +16,18 @@
|
||||
"examples": "start-storybook -p 9001 -c ./examples/.storybook --dont-track",
|
||||
"lint": "eslint src",
|
||||
"prepublish": "npm run build && npm run build:umd",
|
||||
"test": "jest",
|
||||
"test:watch": "npm run test -- --watch"
|
||||
"test": "npm run lint && npm run test:jest",
|
||||
"test:jest": "jest",
|
||||
"test:watch": "npm run test:jest -- --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"animated": "^0.1.3",
|
||||
"array-find-index": "^1.0.2",
|
||||
"babel-runtime": "^6.11.6",
|
||||
"debounce": "^1.0.0",
|
||||
"deep-assign": "^2.0.0",
|
||||
"fbjs": "^0.8.4",
|
||||
"inline-style-prefixer": "^2.0.1",
|
||||
"lodash": "^4.15.0",
|
||||
"react-dom": "~15.3.2",
|
||||
"react-textarea-autosize": "^4.0.4",
|
||||
"react-timer-mixin": "^0.13.3"
|
||||
@@ -33,6 +38,7 @@
|
||||
"babel-core": "^6.14.0",
|
||||
"babel-eslint": "^6.1.2",
|
||||
"babel-loader": "^6.2.5",
|
||||
"babel-plugin-transform-react-remove-prop-types": "^0.2.11",
|
||||
"babel-preset-react-native": "^1.9.0",
|
||||
"del-cli": "^0.2.0",
|
||||
"enzyme": "^2.4.1",
|
||||
@@ -47,7 +53,8 @@
|
||||
"react-addons-test-utils": "~15.3.2",
|
||||
"react-test-renderer": "~15.3.2",
|
||||
"url-loader": "^0.5.7",
|
||||
"webpack": "^1.13.2"
|
||||
"webpack": "^1.13.2",
|
||||
"webpack-bundle-analyzer": "^1.5.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "~15.3.2"
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
/* eslint-env jasmine, jest */
|
||||
|
||||
import { prerenderApplication } from '../renderApplication';
|
||||
import { getApplication } from '../renderApplication';
|
||||
import React from 'react';
|
||||
|
||||
const component = () => <div />;
|
||||
|
||||
describe('apis/AppRegistry/renderApplication', () => {
|
||||
test('prerenderApplication', () => {
|
||||
const { html, styleElement } = prerenderApplication(component, {});
|
||||
test('getApplication', () => {
|
||||
const { element, stylesheet } = getApplication(component, {});
|
||||
|
||||
expect(html.indexOf('<div ') > -1).toBeTruthy();
|
||||
expect(styleElement.type).toEqual('style');
|
||||
expect(element).toBeTruthy();
|
||||
expect(stylesheet.type).toEqual('style');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
|
||||
import { Component } from 'react';
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
import ReactDOM from 'react-dom';
|
||||
import renderApplication, { prerenderApplication } from './renderApplication';
|
||||
import { unmountComponentAtNode } from 'react/lib/ReactMount';
|
||||
import renderApplication, { getApplication } from './renderApplication';
|
||||
|
||||
const runnables = {};
|
||||
|
||||
@@ -29,20 +29,20 @@ class AppRegistry {
|
||||
return Object.keys(runnables);
|
||||
}
|
||||
|
||||
static prerenderApplication(appKey: string, appParameters?: Object): string {
|
||||
static getApplication(appKey: string, appParameters?: Object): string {
|
||||
invariant(
|
||||
runnables[appKey] && runnables[appKey].prerender,
|
||||
runnables[appKey] && runnables[appKey].getApplication,
|
||||
`Application ${appKey} has not been registered. ` +
|
||||
'This is either due to an import error during initialization or failure to call AppRegistry.registerComponent.'
|
||||
);
|
||||
|
||||
return runnables[appKey].prerender(appParameters);
|
||||
return runnables[appKey].getApplication(appParameters);
|
||||
}
|
||||
|
||||
static registerComponent(appKey: string, getComponentFunc: ComponentProvider): string {
|
||||
runnables[appKey] = {
|
||||
run: ({ initialProps, rootTag }) => renderApplication(getComponentFunc(), initialProps, rootTag),
|
||||
prerender: ({ initialProps } = {}) => prerenderApplication(getComponentFunc(), initialProps)
|
||||
getApplication: ({ initialProps } = {}) => getApplication(getComponentFunc(), initialProps),
|
||||
run: ({ initialProps, rootTag }) => renderApplication(getComponentFunc(), initialProps, rootTag)
|
||||
};
|
||||
return appKey;
|
||||
}
|
||||
@@ -85,7 +85,7 @@ class AppRegistry {
|
||||
}
|
||||
|
||||
static unmountApplicationComponentAtRootTag(rootTag) {
|
||||
ReactDOM.unmountComponentAtNode(rootTag);
|
||||
unmountComponentAtNode(rootTag);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
*/
|
||||
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
import ReactDOM from 'react-dom';
|
||||
import ReactDOMServer from 'react-dom/server';
|
||||
import { render } from 'react/lib/ReactMount';
|
||||
import ReactNativeApp from './ReactNativeApp';
|
||||
import StyleSheet from '../../apis/StyleSheet';
|
||||
import React, { Component } from 'react';
|
||||
@@ -23,17 +22,16 @@ export default function renderApplication(RootComponent: Component, initialProps
|
||||
rootTag={rootTag}
|
||||
/>
|
||||
);
|
||||
ReactDOM.render(component, rootTag);
|
||||
render(component, rootTag);
|
||||
}
|
||||
|
||||
export function prerenderApplication(RootComponent: Component, initialProps: Object): string {
|
||||
const component = (
|
||||
export function getApplication(RootComponent: Component, initialProps: Object): Object {
|
||||
const element = (
|
||||
<ReactNativeApp
|
||||
initialProps={initialProps}
|
||||
rootComponent={RootComponent}
|
||||
/>
|
||||
);
|
||||
const html = ReactDOMServer.renderToString(component);
|
||||
const styleElement = StyleSheet.render();
|
||||
return { html, styleElement };
|
||||
const stylesheet = StyleSheet.render();
|
||||
return { element, stylesheet };
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
|
||||
import findIndex from 'lodash/findIndex';
|
||||
import findIndex from 'array-find-index';
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
|
||||
const EVENT_TYPES = [ 'change' ];
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*/
|
||||
import merge from 'lodash/merge';
|
||||
import merge from 'deep-assign';
|
||||
|
||||
const mergeLocalStorageItem = (key, value) => {
|
||||
const oldValue = window.localStorage.getItem(key);
|
||||
|
||||
21
src/apis/Clipboard/index.js
Normal file
21
src/apis/Clipboard/index.js
Normal file
@@ -0,0 +1,21 @@
|
||||
class Clipboard {
|
||||
static getString() {
|
||||
return Promise.resolve('');
|
||||
}
|
||||
|
||||
static setString(text) {
|
||||
let success = false;
|
||||
const textField = document.createElement('textarea');
|
||||
textField.innerText = text;
|
||||
document.body.appendChild(textField);
|
||||
textField.select();
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
success = true;
|
||||
} catch (e) {}
|
||||
document.body.removeChild(textField);
|
||||
return success;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Clipboard;
|
||||
@@ -6,7 +6,7 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import debounce from 'lodash/debounce';
|
||||
import debounce from 'debounce';
|
||||
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
|
||||
|
||||
@@ -6,13 +6,12 @@
|
||||
*/
|
||||
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
import keyMirror from 'fbjs/lib/keyMirror';
|
||||
|
||||
const InteractionManager = {
|
||||
Events: keyMirror({
|
||||
interactionStart: true,
|
||||
interactionComplete: true
|
||||
}),
|
||||
Events: {
|
||||
interactionStart: 'interactionStart',
|
||||
interactionComplete: 'interactionComplete'
|
||||
},
|
||||
|
||||
/**
|
||||
* Schedule a function to run after all interactions have completed.
|
||||
|
||||
36
src/apis/Linking/index.js
Normal file
36
src/apis/Linking/index.js
Normal file
@@ -0,0 +1,36 @@
|
||||
const Linking = {
|
||||
addEventListener() {},
|
||||
removeEventListener() {},
|
||||
canOpenUrl() { return true; },
|
||||
getInitialUrl() { return ''; },
|
||||
openURL(url) {
|
||||
iframeOpen(url);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Tabs opened using JavaScript may redirect the parent tab using
|
||||
* `window.opener.location`, ignoring cross-origin restrictions and enabling
|
||||
* phishing attacks.
|
||||
*
|
||||
* Safari requires that we open the url by injecting a hidden iframe that calls
|
||||
* window.open(), then removes the iframe from the DOM.
|
||||
*
|
||||
* https://mathiasbynens.github.io/rel-noopener/
|
||||
*/
|
||||
const iframeOpen = (url) => {
|
||||
const iframe = document.createElement('iframe');
|
||||
iframe.style.display = 'none';
|
||||
document.body.appendChild(iframe);
|
||||
|
||||
const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
|
||||
const script = iframeDoc.createElement('script');
|
||||
script.text = `
|
||||
window.parent = null; window.top = null; window.frameElement = null;
|
||||
var child = window.open("${url}"); child.opener = null;
|
||||
`;
|
||||
iframeDoc.body.appendChild(script);
|
||||
document.body.removeChild(iframe);
|
||||
};
|
||||
|
||||
module.exports = Linking;
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
|
||||
import findIndex from 'lodash/findIndex';
|
||||
import findIndex from 'array-find-index';
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
|
||||
const connection = ExecutionEnvironment.canUseDOM && (
|
||||
|
||||
@@ -1,9 +1,19 @@
|
||||
/* eslint-env jasmine, jest */
|
||||
|
||||
import { getDefaultStyleSheet } from '../css';
|
||||
import isPlainObject from 'lodash/isPlainObject';
|
||||
import StyleSheet from '..';
|
||||
|
||||
const isPlainObject = (x) => {
|
||||
const toString = Object.prototype.toString;
|
||||
let proto;
|
||||
/* eslint-disable */
|
||||
return (
|
||||
toString.call(x) === '[object Object]' &&
|
||||
(proto = Object.getPrototypeOf(x), proto === null || proto === Object.getPrototypeOf({}))
|
||||
);
|
||||
/* eslint-enable */
|
||||
};
|
||||
|
||||
describe('apis/StyleSheet', () => {
|
||||
beforeEach(() => {
|
||||
StyleSheet._reset();
|
||||
@@ -25,7 +35,7 @@ describe('apis/StyleSheet', () => {
|
||||
|
||||
test('renders a style sheet in the browser', () => {
|
||||
StyleSheet.create({ root: { color: 'red' } });
|
||||
expect(document.getElementById('__react-native-style').textContent).toEqual(getDefaultStyleSheet());
|
||||
expect(document.getElementById('react-native-style__').textContent).toEqual(getDefaultStyleSheet());
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -4,12 +4,11 @@ import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
|
||||
import flattenStyle from '../../modules/flattenStyle';
|
||||
import React from 'react';
|
||||
import ReactNativePropRegistry from '../../modules/ReactNativePropRegistry';
|
||||
import StyleSheetValidation from './StyleSheetValidation';
|
||||
|
||||
let styleElement;
|
||||
let shouldInsertStyleSheet = ExecutionEnvironment.canUseDOM;
|
||||
|
||||
const STYLE_SHEET_ID = '__react-native-style';
|
||||
const STYLE_SHEET_ID = 'react-native-style__';
|
||||
|
||||
const absoluteFillObject = { position: 'absolute', left: 0, right: 0, top: 0, bottom: 0 };
|
||||
|
||||
@@ -52,7 +51,9 @@ module.exports = {
|
||||
|
||||
const result = {};
|
||||
for (const key in styles) {
|
||||
StyleSheetValidation.validateStyle(key, styles);
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
require('./StyleSheetValidation').validateStyle(key, styles);
|
||||
}
|
||||
result[key] = ReactNativePropRegistry.register(styles[key]);
|
||||
}
|
||||
return result;
|
||||
|
||||
5
src/components/Button/__tests__/index-test.js
Normal file
5
src/components/Button/__tests__/index-test.js
Normal file
@@ -0,0 +1,5 @@
|
||||
/* eslint-env jasmine, jest */
|
||||
|
||||
describe('components/Button', () => {
|
||||
test.skip('NO TEST COVERAGE', () => {});
|
||||
});
|
||||
66
src/components/Button/index.js
Normal file
66
src/components/Button/index.js
Normal file
@@ -0,0 +1,66 @@
|
||||
import ColorPropType from '../../propTypes/ColorPropType';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import StyleSheet from '../../apis/StyleSheet';
|
||||
import TouchableOpacity from '../Touchable/TouchableOpacity';
|
||||
import Text from '../Text';
|
||||
|
||||
class Button extends Component {
|
||||
static propTypes = {
|
||||
accessibilityLabel: PropTypes.string,
|
||||
color: ColorPropType,
|
||||
disabled: PropTypes.bool,
|
||||
onPress: PropTypes.func.isRequired,
|
||||
title: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
accessibilityLabel,
|
||||
color,
|
||||
disabled,
|
||||
onPress,
|
||||
title
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
accessibilityLabel={accessibilityLabel}
|
||||
accessibilityRole={'button'}
|
||||
disabled={disabled}
|
||||
onPress={onPress}
|
||||
style={[
|
||||
styles.button,
|
||||
color && { backgroundColor: color },
|
||||
disabled && styles.buttonDisabled
|
||||
]}>
|
||||
<Text style={[
|
||||
styles.text,
|
||||
disabled && styles.textDisabled
|
||||
]}>
|
||||
{title}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
button: {
|
||||
backgroundColor: '#2196F3',
|
||||
borderRadius: 2
|
||||
},
|
||||
text: {
|
||||
textAlign: 'center',
|
||||
color: '#fff',
|
||||
padding: 8,
|
||||
fontWeight: '500'
|
||||
},
|
||||
buttonDisabled: {
|
||||
backgroundColor: '#dfdfdf'
|
||||
},
|
||||
textDisabled: {
|
||||
color: '#a1a1a1'
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Button;
|
||||
@@ -7,7 +7,7 @@ import TransformPropTypes from '../../propTypes/TransformPropTypes';
|
||||
|
||||
const hiddenOrVisible = PropTypes.oneOf([ 'hidden', 'visible' ]);
|
||||
|
||||
module.exports = {
|
||||
module.exports = process.env.NODE_ENV !== 'production' ? {
|
||||
...BorderPropTypes,
|
||||
...LayoutPropTypes,
|
||||
...TransformPropTypes,
|
||||
@@ -24,4 +24,4 @@ module.exports = {
|
||||
* @platform web
|
||||
*/
|
||||
visibility: hiddenOrVisible
|
||||
};
|
||||
} : {};
|
||||
|
||||
@@ -1,3 +1,60 @@
|
||||
exports[`components/Image passes other props through to underlying View 1`] = `
|
||||
<div
|
||||
className=" __style_df"
|
||||
onResponderGrant={[Function]}
|
||||
role="img"
|
||||
style={
|
||||
Object {
|
||||
"MozBoxSizing": "border-box",
|
||||
"WebkitAlignItems": "stretch",
|
||||
"WebkitBoxAlign": "stretch",
|
||||
"WebkitBoxDirection": "normal",
|
||||
"WebkitBoxOrient": "vertical",
|
||||
"WebkitFlexBasis": "auto",
|
||||
"WebkitFlexDirection": "column",
|
||||
"WebkitFlexShrink": 0,
|
||||
"alignItems": "stretch",
|
||||
"backgroundColor": "transparent",
|
||||
"backgroundPosition": "center",
|
||||
"backgroundRepeat": "no-repeat",
|
||||
"backgroundSize": "cover",
|
||||
"borderBottomStyle": "solid",
|
||||
"borderBottomWidth": "0px",
|
||||
"borderLeftStyle": "solid",
|
||||
"borderLeftWidth": "0px",
|
||||
"borderRightStyle": "solid",
|
||||
"borderRightWidth": "0px",
|
||||
"borderTopStyle": "solid",
|
||||
"borderTopWidth": "0px",
|
||||
"boxSizing": "border-box",
|
||||
"color": "inherit",
|
||||
"display": null,
|
||||
"flexBasis": "auto",
|
||||
"flexDirection": "column",
|
||||
"flexShrink": 0,
|
||||
"font": "inherit",
|
||||
"listStyle": "none",
|
||||
"marginBottom": "0px",
|
||||
"marginLeft": "0px",
|
||||
"marginRight": "0px",
|
||||
"marginTop": "0px",
|
||||
"minHeight": "0px",
|
||||
"minWidth": "0px",
|
||||
"msFlexAlign": "stretch",
|
||||
"msFlexDirection": "column",
|
||||
"msFlexNegative": 0,
|
||||
"msPreferredSize": "auto",
|
||||
"paddingBottom": "0px",
|
||||
"paddingLeft": "0px",
|
||||
"paddingRight": "0px",
|
||||
"paddingTop": "0px",
|
||||
"position": "relative",
|
||||
"textAlign": "inherit",
|
||||
"textDecoration": "none",
|
||||
}
|
||||
} />
|
||||
`;
|
||||
|
||||
exports[`components/Image prop "accessibilityLabel" 1`] = `
|
||||
<div
|
||||
aria-label="accessibilityLabel"
|
||||
|
||||
@@ -91,4 +91,10 @@ describe('components/Image', () => {
|
||||
const component = renderer.create(<Image testID='testID' />);
|
||||
expect(component.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('passes other props through to underlying View', () => {
|
||||
const fn = () => {};
|
||||
const component = renderer.create(<Image onResponderGrant={fn} />);
|
||||
expect(component.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/* global window */
|
||||
import applyNativeMethods from '../../modules/applyNativeMethods';
|
||||
import BaseComponentPropTypes from '../../propTypes/BaseComponentPropTypes';
|
||||
import ImageResizeMode from './ImageResizeMode';
|
||||
import ImageStylePropTypes from './ImageStylePropTypes';
|
||||
import requestAnimationFrame from 'fbjs/lib/requestAnimationFrame';
|
||||
import StyleSheet from '../../apis/StyleSheet';
|
||||
import StyleSheetPropType from '../../propTypes/StyleSheetPropType';
|
||||
import View from '../View';
|
||||
@@ -38,7 +38,7 @@ class Image extends Component {
|
||||
static displayName = 'Image';
|
||||
|
||||
static propTypes = {
|
||||
...BaseComponentPropTypes,
|
||||
...View.propTypes,
|
||||
children: PropTypes.any,
|
||||
defaultSource: ImageSourcePropType,
|
||||
onError: PropTypes.func,
|
||||
@@ -99,27 +99,32 @@ class Image extends Component {
|
||||
defaultSource,
|
||||
onLayout,
|
||||
source,
|
||||
testID
|
||||
testID,
|
||||
/* eslint-disable */
|
||||
resizeMode,
|
||||
/* eslint-enable */
|
||||
...other
|
||||
} = this.props;
|
||||
|
||||
const displayImage = resolveAssetSource(!isLoaded ? defaultSource : source);
|
||||
const imageSizeStyle = resolveAssetDimensions(!isLoaded ? defaultSource : source);
|
||||
const backgroundImage = displayImage ? `url("${displayImage}")` : null;
|
||||
const originalStyle = StyleSheet.flatten(this.props.style);
|
||||
const resizeMode = this.props.resizeMode || originalStyle.resizeMode || ImageResizeMode.cover;
|
||||
const finalResizeMode = resizeMode || originalStyle.resizeMode || ImageResizeMode.cover;
|
||||
|
||||
const style = StyleSheet.flatten([
|
||||
styles.initial,
|
||||
imageSizeStyle,
|
||||
originalStyle,
|
||||
backgroundImage && { backgroundImage },
|
||||
resizeModeStyles[resizeMode]
|
||||
resizeModeStyles[finalResizeMode]
|
||||
]);
|
||||
// View doesn't support 'resizeMode' as a style
|
||||
delete style.resizeMode;
|
||||
|
||||
return (
|
||||
<View
|
||||
{...other}
|
||||
accessibilityLabel={accessibilityLabel}
|
||||
accessibilityRole='img'
|
||||
accessible={accessible}
|
||||
@@ -189,7 +194,7 @@ class Image extends Component {
|
||||
this._imageState = status;
|
||||
const isLoaded = this._imageState === STATUS_LOADED;
|
||||
if (isLoaded !== this.state.isLoaded) {
|
||||
window.requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
if (this._isMounted) {
|
||||
this.setState({ isLoaded });
|
||||
}
|
||||
|
||||
@@ -1,22 +1,28 @@
|
||||
import applyNativeMethods from '../../modules/applyNativeMethods';
|
||||
import ListViewDataSource from './ListViewDataSource';
|
||||
import ListViewPropTypes from './ListViewPropTypes';
|
||||
import pick from 'lodash/pick';
|
||||
import ScrollView from '../ScrollView';
|
||||
import View from '../View';
|
||||
import React, { Component } from 'react';
|
||||
import StaticRenderer from '../StaticRenderer';
|
||||
import React, { Component, isEmpty, merge } from 'react';
|
||||
import requestAnimationFrame from 'fbjs/lib/requestAnimationFrame';
|
||||
|
||||
const scrollViewProps = Object.keys(ScrollView.propTypes);
|
||||
const DEFAULT_PAGE_SIZE = 1;
|
||||
const DEFAULT_INITIAL_ROWS = 10;
|
||||
const DEFAULT_SCROLL_RENDER_AHEAD = 1000;
|
||||
const DEFAULT_END_REACHED_THRESHOLD = 1000;
|
||||
const DEFAULT_SCROLL_CALLBACK_THROTTLE = 50;
|
||||
|
||||
class ListView extends Component {
|
||||
static propTypes = ListViewPropTypes;
|
||||
|
||||
static defaultProps = {
|
||||
initialListSize: 10,
|
||||
pageSize: 1,
|
||||
initialListSize: DEFAULT_INITIAL_ROWS,
|
||||
pageSize: DEFAULT_PAGE_SIZE,
|
||||
renderScrollComponent: (props) => <ScrollView {...props} />,
|
||||
scrollRenderAheadDistance: 1000,
|
||||
onEndReachedThreshold: 1000,
|
||||
scrollRenderAheadDistance: DEFAULT_SCROLL_RENDER_AHEAD,
|
||||
onEndReachedThreshold: DEFAULT_END_REACHED_THRESHOLD,
|
||||
scrollEventThrottle: DEFAULT_SCROLL_CALLBACK_THROTTLE,
|
||||
removeClippedSubviews: true,
|
||||
stickyHeaderIndices: []
|
||||
};
|
||||
|
||||
@@ -29,6 +35,34 @@ class ListView extends Component {
|
||||
highlightedRow: {}
|
||||
};
|
||||
this.onRowHighlighted = (sectionId, rowId) => this._onRowHighlighted(sectionId, rowId);
|
||||
this.scrollProperties = {};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
// this data should never trigger a render pass, so don't put in state
|
||||
this.scrollProperties = {
|
||||
visibleLength: null,
|
||||
contentLength: null,
|
||||
offset: 0
|
||||
};
|
||||
this._childFrames = [];
|
||||
this._visibleRows = {};
|
||||
this._prevRenderedRowsCount = 0;
|
||||
this._sentEndForContentLength = null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// do this in animation frame until componentDidMount actually runs after
|
||||
// the component is laid out
|
||||
requestAnimationFrame(() => {
|
||||
this._measureAndUpdateScrollProps();
|
||||
});
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
requestAnimationFrame(() => {
|
||||
this._measureAndUpdateScrollProps();
|
||||
});
|
||||
}
|
||||
|
||||
getScrollResponder() {
|
||||
@@ -43,60 +77,313 @@ class ListView extends Component {
|
||||
return this._scrollViewRef && this._scrollViewRef.setNativeProps(props);
|
||||
}
|
||||
|
||||
_onRowHighlighted(sectionId, rowId) {
|
||||
_onRowHighlighted = (sectionId, rowId) => {
|
||||
this.setState({ highlightedRow: { sectionId, rowId } });
|
||||
}
|
||||
|
||||
renderSectionHeaderFn = (data, sectionID) => {
|
||||
return () => this.props.renderSectionHeader(data, sectionID);
|
||||
}
|
||||
|
||||
renderRowFn = (data, sectionID, rowID) => {
|
||||
return () => this.props.renderRow(data, sectionID, rowID, this._onRowHighlighted);
|
||||
}
|
||||
|
||||
render() {
|
||||
const dataSource = this.props.dataSource;
|
||||
const header = this.props.renderHeader ? this.props.renderHeader() : undefined;
|
||||
const footer = this.props.renderFooter ? this.props.renderFooter() : undefined;
|
||||
|
||||
// render sections and rows
|
||||
const children = [];
|
||||
const sections = dataSource.rowIdentities;
|
||||
const renderRow = this.props.renderRow;
|
||||
const renderSectionHeader = this.props.renderSectionHeader;
|
||||
const renderSeparator = this.props.renderSeparator;
|
||||
for (let sectionIdx = 0, sectionCnt = sections.length; sectionIdx < sectionCnt; sectionIdx++) {
|
||||
const rows = sections[sectionIdx];
|
||||
const sectionId = dataSource.sectionIdentities[sectionIdx];
|
||||
|
||||
// render optional section header
|
||||
if (renderSectionHeader) {
|
||||
const section = dataSource.getSectionHeaderData(sectionIdx);
|
||||
const key = `s_${sectionId}`;
|
||||
const child = <View key={key}>{renderSectionHeader(section, sectionId)}</View>;
|
||||
children.push(child);
|
||||
const dataSource = this.props.dataSource;
|
||||
const allRowIDs = dataSource.rowIdentities;
|
||||
let rowCount = 0;
|
||||
const sectionHeaderIndices = [];
|
||||
|
||||
const header = this.props.renderHeader && this.props.renderHeader();
|
||||
const footer = this.props.renderFooter && this.props.renderFooter();
|
||||
let totalIndex = header ? 1 : 0;
|
||||
|
||||
for (let sectionIdx = 0; sectionIdx < allRowIDs.length; sectionIdx++) {
|
||||
const sectionID = dataSource.sectionIdentities[sectionIdx];
|
||||
const rowIDs = allRowIDs[sectionIdx];
|
||||
if (rowIDs.length === 0) {
|
||||
if (this.props.enableEmptySections === undefined) {
|
||||
const warning = require('fbjs/lib/warning');
|
||||
warning(false, 'In next release empty section headers will be rendered.' +
|
||||
' In this release you can use \'enableEmptySections\' flag to render empty section headers.');
|
||||
continue;
|
||||
} else {
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
invariant(
|
||||
this.props.enableEmptySections,
|
||||
'In next release \'enableEmptySections\' flag will be deprecated,' +
|
||||
' empty section headers will always be rendered. If empty section headers' +
|
||||
' are not desirable their indices should be excluded from sectionIDs object.' +
|
||||
' In this release \'enableEmptySections\' may only have value \'true\'' +
|
||||
' to allow empty section headers rendering.');
|
||||
}
|
||||
}
|
||||
|
||||
// render rows
|
||||
for (let rowIdx = 0, rowCnt = rows.length; rowIdx < rowCnt; rowIdx++) {
|
||||
const rowId = rows[rowIdx];
|
||||
const row = dataSource.getRowData(sectionIdx, rowIdx);
|
||||
const key = `r_${sectionId}_${rowId}`;
|
||||
const child = <View key={key}>{renderRow(row, sectionId, rowId, this.onRowHighlighted)}</View>;
|
||||
children.push(child);
|
||||
if (this.props.renderSectionHeader) {
|
||||
const shouldUpdateHeader = rowCount >= this._prevRenderedRowsCount &&
|
||||
dataSource.sectionHeaderShouldUpdate(sectionIdx);
|
||||
children.push(
|
||||
<StaticRenderer
|
||||
key={`s_${sectionID}`}
|
||||
render={this.renderSectionHeaderFn(
|
||||
dataSource.getSectionHeaderData(sectionIdx),
|
||||
sectionID
|
||||
)}
|
||||
shouldUpdate={!!shouldUpdateHeader}
|
||||
/>
|
||||
);
|
||||
sectionHeaderIndices.push(totalIndex++);
|
||||
}
|
||||
|
||||
// render optional separator
|
||||
if (renderSeparator && ((rowIdx !== rows.length - 1) || (sectionIdx === sections.length - 1))) {
|
||||
for (let rowIdx = 0; rowIdx < rowIDs.length; rowIdx++) {
|
||||
const rowID = rowIDs[rowIdx];
|
||||
const comboID = `${sectionID}_${rowID}`;
|
||||
const shouldUpdateRow = rowCount >= this._prevRenderedRowsCount &&
|
||||
dataSource.rowShouldUpdate(sectionIdx, rowIdx);
|
||||
const row =
|
||||
<StaticRenderer
|
||||
key={`r_${comboID}`}
|
||||
render={this.renderRowFn(
|
||||
dataSource.getRowData(sectionIdx, rowIdx),
|
||||
sectionID,
|
||||
rowID
|
||||
)}
|
||||
shouldUpdate={!!shouldUpdateRow}
|
||||
/>;
|
||||
children.push(row);
|
||||
totalIndex++;
|
||||
|
||||
if (this.props.renderSeparator &&
|
||||
(rowIdx !== rowIDs.length - 1 || sectionIdx === allRowIDs.length - 1)) {
|
||||
const adjacentRowHighlighted =
|
||||
this.state.highlightedRow.sectionID === sectionId && (
|
||||
this.state.highlightedRow.rowID === rowId ||
|
||||
this.state.highlightedRow.rowID === rows[rowIdx + 1]);
|
||||
const separator = renderSeparator(sectionId, rowId, adjacentRowHighlighted);
|
||||
children.push(separator);
|
||||
this.state.highlightedRow.sectionID === sectionID && (
|
||||
this.state.highlightedRow.rowID === rowID ||
|
||||
this.state.highlightedRow.rowID === rowIDs[rowIdx + 1]
|
||||
);
|
||||
const separator = this.props.renderSeparator(
|
||||
sectionID,
|
||||
rowID,
|
||||
adjacentRowHighlighted
|
||||
);
|
||||
if (separator) {
|
||||
children.push(separator);
|
||||
totalIndex++;
|
||||
}
|
||||
}
|
||||
if (++rowCount === this.state.curRenderedRowsCount) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (rowCount >= this.state.curRenderedRowsCount) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const props = pick(this.props, scrollViewProps);
|
||||
const {
|
||||
renderScrollComponent,
|
||||
...props
|
||||
} = this.props;
|
||||
Object.assign(props, {
|
||||
onScroll: this._onScroll,
|
||||
stickyHeaderIndices: this.props.stickyHeaderIndices.concat(sectionHeaderIndices),
|
||||
|
||||
return React.cloneElement(this.props.renderScrollComponent(props), {
|
||||
ref: this._setScrollViewRef
|
||||
// Do not pass these events downstream to ScrollView since they will be
|
||||
// registered in ListView's own ScrollResponder.Mixin
|
||||
onKeyboardWillShow: undefined,
|
||||
onKeyboardWillHide: undefined,
|
||||
onKeyboardDidShow: undefined,
|
||||
onKeyboardDidHide: undefined
|
||||
});
|
||||
|
||||
return React.cloneElement(renderScrollComponent(props), {
|
||||
ref: this._setScrollViewRef,
|
||||
onContentSizeChange: this._onContentSizeChange,
|
||||
onLayout: this._onLayout
|
||||
}, header, children, footer);
|
||||
}
|
||||
|
||||
_measureAndUpdateScrollProps() {
|
||||
const scrollComponent = this.getScrollResponder();
|
||||
if (!scrollComponent || !scrollComponent.getInnerViewNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._updateVisibleRows();
|
||||
}
|
||||
|
||||
_onLayout = (event: Object) => {
|
||||
const { width, height } = event.nativeEvent.layout;
|
||||
const visibleLength = !this.props.horizontal ? height : width;
|
||||
if (visibleLength !== this.scrollProperties.visibleLength) {
|
||||
this.scrollProperties.visibleLength = visibleLength;
|
||||
this._updateVisibleRows();
|
||||
this._renderMoreRowsIfNeeded();
|
||||
}
|
||||
this.props.onLayout && this.props.onLayout(event);
|
||||
}
|
||||
|
||||
_updateVisibleRows(updatedFrames?: Array<Object>) {
|
||||
if (!this.props.onChangeVisibleRows) {
|
||||
return; // No need to compute visible rows if there is no callback
|
||||
}
|
||||
if (updatedFrames) {
|
||||
updatedFrames.forEach((newFrame) => {
|
||||
this._childFrames[newFrame.index] = merge(newFrame);
|
||||
});
|
||||
}
|
||||
const isVertical = !this.props.horizontal;
|
||||
const dataSource = this.props.dataSource;
|
||||
const visibleMin = this.scrollProperties.offset;
|
||||
const visibleMax = visibleMin + this.scrollProperties.visibleLength;
|
||||
const allRowIDs = dataSource.rowIdentities;
|
||||
|
||||
const header = this.props.renderHeader && this.props.renderHeader();
|
||||
let totalIndex = header ? 1 : 0;
|
||||
let visibilityChanged = false;
|
||||
const changedRows = {};
|
||||
for (let sectionIdx = 0; sectionIdx < allRowIDs.length; sectionIdx++) {
|
||||
const rowIDs = allRowIDs[sectionIdx];
|
||||
if (rowIDs.length === 0) {
|
||||
continue;
|
||||
}
|
||||
const sectionID = dataSource.sectionIdentities[sectionIdx];
|
||||
if (this.props.renderSectionHeader) {
|
||||
totalIndex++;
|
||||
}
|
||||
let visibleSection = this._visibleRows[sectionID];
|
||||
if (!visibleSection) {
|
||||
visibleSection = {};
|
||||
}
|
||||
for (let rowIdx = 0; rowIdx < rowIDs.length; rowIdx++) {
|
||||
const rowID = rowIDs[rowIdx];
|
||||
const frame = this._childFrames[totalIndex];
|
||||
totalIndex++;
|
||||
if (this.props.renderSeparator &&
|
||||
(rowIdx !== rowIDs.length - 1 || sectionIdx === allRowIDs.length - 1)) {
|
||||
totalIndex++;
|
||||
}
|
||||
if (!frame) {
|
||||
break;
|
||||
}
|
||||
const rowVisible = visibleSection[rowID];
|
||||
const min = isVertical ? frame.y : frame.x;
|
||||
const max = min + (isVertical ? frame.height : frame.width);
|
||||
if ((!min && !max) || (min === max)) {
|
||||
break;
|
||||
}
|
||||
if (min > visibleMax || max < visibleMin) {
|
||||
if (rowVisible) {
|
||||
visibilityChanged = true;
|
||||
delete visibleSection[rowID];
|
||||
if (!changedRows[sectionID]) {
|
||||
changedRows[sectionID] = {};
|
||||
}
|
||||
changedRows[sectionID][rowID] = false;
|
||||
}
|
||||
} else if (!rowVisible) {
|
||||
visibilityChanged = true;
|
||||
visibleSection[rowID] = true;
|
||||
if (!changedRows[sectionID]) {
|
||||
changedRows[sectionID] = {};
|
||||
}
|
||||
changedRows[sectionID][rowID] = true;
|
||||
}
|
||||
}
|
||||
if (!isEmpty(visibleSection)) {
|
||||
this._visibleRows[sectionID] = visibleSection;
|
||||
} else if (this._visibleRows[sectionID]) {
|
||||
delete this._visibleRows[sectionID];
|
||||
}
|
||||
}
|
||||
visibilityChanged && this.props.onChangeVisibleRows(this._visibleRows, changedRows);
|
||||
}
|
||||
|
||||
_onContentSizeChange = (width: number, height: number) => {
|
||||
const contentLength = !this.props.horizontal ? height : width;
|
||||
if (contentLength !== this.scrollProperties.contentLength) {
|
||||
this.scrollProperties.contentLength = contentLength;
|
||||
this._updateVisibleRows();
|
||||
this._renderMoreRowsIfNeeded();
|
||||
}
|
||||
this.props.onContentSizeChange && this.props.onContentSizeChange(width, height);
|
||||
}
|
||||
|
||||
_getDistanceFromEnd(scrollProperties: Object) {
|
||||
return scrollProperties.contentLength - scrollProperties.visibleLength - scrollProperties.offset;
|
||||
}
|
||||
|
||||
_maybeCallOnEndReached(event?: Object) {
|
||||
if (this.props.onEndReached &&
|
||||
this.scrollProperties.contentLength !== this._sentEndForContentLength &&
|
||||
this._getDistanceFromEnd(this.scrollProperties) < this.props.onEndReachedThreshold &&
|
||||
this.state.curRenderedRowsCount === (this.props.enableEmptySections ?
|
||||
this.props.dataSource.getRowAndSectionCount() : this.props.dataSource.getRowCount())) {
|
||||
this._sentEndForContentLength = this.scrollProperties.contentLength;
|
||||
this.props.onEndReached(event);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
_renderMoreRowsIfNeeded() {
|
||||
if (this.scrollProperties.contentLength === null ||
|
||||
this.scrollProperties.visibleLength === null ||
|
||||
this.state.curRenderedRowsCount === (this.props.enableEmptySections ?
|
||||
this.props.dataSource.getRowAndSectionCount() : this.props.dataSource.getRowCount())) {
|
||||
this._maybeCallOnEndReached();
|
||||
return;
|
||||
}
|
||||
|
||||
const distanceFromEnd = this._getDistanceFromEnd(this.scrollProperties);
|
||||
if (distanceFromEnd < this.props.scrollRenderAheadDistance) {
|
||||
this._pageInNewRows();
|
||||
}
|
||||
}
|
||||
|
||||
_pageInNewRows() {
|
||||
this.setState((state, props) => {
|
||||
const rowsToRender = Math.min(
|
||||
state.curRenderedRowsCount + props.pageSize,
|
||||
(props.enableEmptySections ? props.dataSource.getRowAndSectionCount() : props.dataSource.getRowCount())
|
||||
);
|
||||
this._prevRenderedRowsCount = state.curRenderedRowsCount;
|
||||
return {
|
||||
curRenderedRowsCount: rowsToRender
|
||||
};
|
||||
}, () => {
|
||||
this._measureAndUpdateScrollProps();
|
||||
this._prevRenderedRowsCount = this.state.curRenderedRowsCount;
|
||||
});
|
||||
}
|
||||
|
||||
_onScroll = (e: Object) => {
|
||||
const isVertical = !this.props.horizontal;
|
||||
this.scrollProperties.visibleLength = e.nativeEvent.layoutMeasurement[
|
||||
isVertical ? 'height' : 'width'
|
||||
];
|
||||
this.scrollProperties.contentLength = e.nativeEvent.contentSize[
|
||||
isVertical ? 'height' : 'width'
|
||||
];
|
||||
this.scrollProperties.offset = e.nativeEvent.contentOffset[
|
||||
isVertical ? 'y' : 'x'
|
||||
];
|
||||
this._updateVisibleRows(e.nativeEvent.updatedChildFrames);
|
||||
if (!this._maybeCallOnEndReached(e)) {
|
||||
this._renderMoreRowsIfNeeded();
|
||||
}
|
||||
|
||||
if (this.props.onEndReached &&
|
||||
this._getDistanceFromEnd(this.scrollProperties) > this.props.onEndReachedThreshold) {
|
||||
// Scrolled out of the end zone, so it should be able to trigger again.
|
||||
this._sentEndForContentLength = null;
|
||||
}
|
||||
|
||||
this.props.onScroll && this.props.onScroll(e);
|
||||
};
|
||||
|
||||
_setScrollViewRef = (component) => {
|
||||
this._scrollViewRef = component;
|
||||
}
|
||||
|
||||
@@ -6,10 +6,39 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import debounce from 'lodash/debounce';
|
||||
import debounce from 'debounce';
|
||||
import View from '../View';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
|
||||
const normalizeScrollEvent = (e) => ({
|
||||
nativeEvent: {
|
||||
contentOffset: {
|
||||
get x() {
|
||||
return e.target.scrollLeft;
|
||||
},
|
||||
get y() {
|
||||
return e.target.scrollTop;
|
||||
}
|
||||
},
|
||||
contentSize: {
|
||||
get height() {
|
||||
return e.target.scrollHeight;
|
||||
},
|
||||
get width() {
|
||||
return e.target.scrollWidth;
|
||||
}
|
||||
},
|
||||
layoutMeasurement: {
|
||||
get height() {
|
||||
return e.target.offsetHeight;
|
||||
},
|
||||
get width() {
|
||||
return e.target.offsetWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Encapsulates the Web-specific scroll throttling and disabling logic
|
||||
*/
|
||||
@@ -23,12 +52,16 @@ export default class ScrollViewBase extends Component {
|
||||
onScrollEndDrag: PropTypes.func,
|
||||
onTouchMove: PropTypes.func,
|
||||
onWheel: PropTypes.func,
|
||||
removeClippedSubviews: PropTypes.bool,
|
||||
scrollEnabled: PropTypes.bool,
|
||||
scrollEventThrottle: PropTypes.number
|
||||
scrollEventThrottle: PropTypes.number,
|
||||
showsHorizontalScrollIndicator: PropTypes.bool,
|
||||
showsVerticalScrollIndicator: PropTypes.bool
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
scrollEnabled: true
|
||||
scrollEnabled: true,
|
||||
scrollEventThrottle: 0
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
@@ -48,6 +81,7 @@ export default class ScrollViewBase extends Component {
|
||||
}
|
||||
|
||||
_handleScroll = (e) => {
|
||||
e.persist();
|
||||
const { scrollEventThrottle } = this.props;
|
||||
// A scroll happened, so the scroll bumps the debounce.
|
||||
this._debouncedOnScrollEnd(e);
|
||||
@@ -70,23 +104,33 @@ export default class ScrollViewBase extends Component {
|
||||
_handleScrollTick(e) {
|
||||
const { onScroll } = this.props;
|
||||
this._state.scrollLastTick = Date.now();
|
||||
if (onScroll) { onScroll(e); }
|
||||
if (onScroll) { onScroll(normalizeScrollEvent(e)); }
|
||||
}
|
||||
|
||||
_handleScrollEnd(e) {
|
||||
const { onScroll } = this.props;
|
||||
this._state.isScrolling = false;
|
||||
if (onScroll) { onScroll(e); }
|
||||
if (onScroll) { onScroll(normalizeScrollEvent(e)); }
|
||||
}
|
||||
|
||||
_shouldEmitScrollEvent(lastTick, eventThrottle) {
|
||||
const timeSinceLastTick = Date.now() - lastTick;
|
||||
return (eventThrottle > 0 && timeSinceLastTick >= (1000 / eventThrottle));
|
||||
return (eventThrottle > 0 && timeSinceLastTick >= eventThrottle);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
onMomentumScrollBegin, onMomentumScrollEnd, onScrollBeginDrag, onScrollEndDrag, scrollEnabled, scrollEventThrottle, // eslint-disable-line
|
||||
/* eslint-disable */
|
||||
onMomentumScrollBegin,
|
||||
onMomentumScrollEnd,
|
||||
onScrollBeginDrag,
|
||||
onScrollEndDrag,
|
||||
removeClippedSubviews,
|
||||
scrollEnabled,
|
||||
scrollEventThrottle,
|
||||
showsHorizontalScrollIndicator,
|
||||
showsVerticalScrollIndicator,
|
||||
/* eslint-enable */
|
||||
...other
|
||||
} = this.props;
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
*/
|
||||
|
||||
import dismissKeyboard from '../../modules/dismissKeyboard';
|
||||
import findNodeHandle from '../../modules/findNodeHandle';
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
import ReactDOM from 'react-dom';
|
||||
import ScrollResponder from '../../modules/ScrollResponder';
|
||||
import ScrollViewBase from './ScrollViewBase';
|
||||
import StyleSheet from '../../apis/StyleSheet';
|
||||
@@ -21,12 +21,12 @@ import React, { Component, PropTypes } from 'react';
|
||||
const ScrollView = React.createClass({
|
||||
propTypes: {
|
||||
...View.propTypes,
|
||||
children: View.propTypes.children,
|
||||
contentContainerStyle: StyleSheetPropType(ViewStylePropTypes),
|
||||
horizontal: PropTypes.bool,
|
||||
keyboardDismissMode: PropTypes.oneOf([ 'none', 'interactive', 'on-drag' ]),
|
||||
onContentSizeChange: PropTypes.func,
|
||||
onScroll: PropTypes.func,
|
||||
pagingEnabled: PropTypes.bool,
|
||||
refreshControl: PropTypes.element,
|
||||
scrollEnabled: PropTypes.bool,
|
||||
scrollEventThrottle: PropTypes.number,
|
||||
@@ -54,11 +54,11 @@ const ScrollView = React.createClass({
|
||||
},
|
||||
|
||||
getScrollableNode(): any {
|
||||
return ReactDOM.findDOMNode(this._scrollViewRef);
|
||||
return findNodeHandle(this._scrollViewRef);
|
||||
},
|
||||
|
||||
getInnerViewNode(): any {
|
||||
return ReactDOM.findDOMNode(this._innerViewRef);
|
||||
return findNodeHandle(this._innerViewRef);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -97,10 +97,13 @@ const ScrollView = React.createClass({
|
||||
const {
|
||||
contentContainerStyle,
|
||||
horizontal,
|
||||
keyboardDismissMode, // eslint-disable-line
|
||||
onContentSizeChange,
|
||||
onScroll, // eslint-disable-line
|
||||
refreshControl,
|
||||
/* eslint-disable */
|
||||
keyboardDismissMode,
|
||||
onScroll,
|
||||
pagingEnabled,
|
||||
/* eslint-enable */
|
||||
...other
|
||||
} = this.props;
|
||||
|
||||
@@ -233,7 +236,7 @@ const styles = StyleSheet.create({
|
||||
overflowY: 'hidden'
|
||||
},
|
||||
contentContainer: {
|
||||
flexGrow: 1
|
||||
transform: [ { translateZ: 0 } ]
|
||||
},
|
||||
contentContainerHorizontal: {
|
||||
flexDirection: 'row'
|
||||
|
||||
@@ -67,7 +67,7 @@ exports[`components/Switch disabled when "false" a default checkbox is rendered
|
||||
"WebkitFlexBasis": "auto",
|
||||
"WebkitFlexDirection": "column",
|
||||
"WebkitFlexShrink": 0,
|
||||
"WebkitTransition": "background-color 0.1s",
|
||||
"WebkitTransition": "0.1s",
|
||||
"alignItems": "stretch",
|
||||
"backgroundColor": "#939393",
|
||||
"borderBottomLeftRadius": "10px",
|
||||
@@ -112,7 +112,7 @@ exports[`components/Switch disabled when "false" a default checkbox is rendered
|
||||
"textAlign": "inherit",
|
||||
"textDecoration": "none",
|
||||
"top": "0px",
|
||||
"transition": "background-color 0.1s",
|
||||
"transition": "0.1s",
|
||||
"width": "90%",
|
||||
}
|
||||
} />
|
||||
@@ -129,7 +129,8 @@ exports[`components/Switch disabled when "false" a default checkbox is rendered
|
||||
"WebkitFlexBasis": "auto",
|
||||
"WebkitFlexDirection": "column",
|
||||
"WebkitFlexShrink": 0,
|
||||
"WebkitTransition": "background-color 0.1s",
|
||||
"WebkitTransform": "translateX(0%)",
|
||||
"WebkitTransition": "0.1s",
|
||||
"alignItems": "stretch",
|
||||
"alignSelf": "flex-start",
|
||||
"backgroundColor": "#FAFAFA",
|
||||
@@ -166,6 +167,7 @@ exports[`components/Switch disabled when "false" a default checkbox is rendered
|
||||
"msFlexItemAlign": "start",
|
||||
"msFlexNegative": 0,
|
||||
"msPreferredSize": "auto",
|
||||
"msTransform": "translateX(0%)",
|
||||
"paddingBottom": "0px",
|
||||
"paddingLeft": "0px",
|
||||
"paddingRight": "0px",
|
||||
@@ -173,7 +175,8 @@ exports[`components/Switch disabled when "false" a default checkbox is rendered
|
||||
"position": "relative",
|
||||
"textAlign": "inherit",
|
||||
"textDecoration": "none",
|
||||
"transition": "background-color 0.1s",
|
||||
"transform": "translateX(0%)",
|
||||
"transition": "0.1s",
|
||||
"width": "20px",
|
||||
}
|
||||
} />
|
||||
@@ -278,7 +281,7 @@ exports[`components/Switch disabled when "true" a disabled checkbox is rendered
|
||||
"WebkitFlexBasis": "auto",
|
||||
"WebkitFlexDirection": "column",
|
||||
"WebkitFlexShrink": 0,
|
||||
"WebkitTransition": "background-color 0.1s",
|
||||
"WebkitTransition": "0.1s",
|
||||
"alignItems": "stretch",
|
||||
"backgroundColor": "#D5D5D5",
|
||||
"borderBottomLeftRadius": "10px",
|
||||
@@ -323,7 +326,7 @@ exports[`components/Switch disabled when "true" a disabled checkbox is rendered
|
||||
"textAlign": "inherit",
|
||||
"textDecoration": "none",
|
||||
"top": "0px",
|
||||
"transition": "background-color 0.1s",
|
||||
"transition": "0.1s",
|
||||
"width": "90%",
|
||||
}
|
||||
} />
|
||||
@@ -340,7 +343,8 @@ exports[`components/Switch disabled when "true" a disabled checkbox is rendered
|
||||
"WebkitFlexBasis": "auto",
|
||||
"WebkitFlexDirection": "column",
|
||||
"WebkitFlexShrink": 0,
|
||||
"WebkitTransition": "background-color 0.1s",
|
||||
"WebkitTransform": "translateX(0%)",
|
||||
"WebkitTransition": "0.1s",
|
||||
"alignItems": "stretch",
|
||||
"alignSelf": "flex-start",
|
||||
"backgroundColor": "#BDBDBD",
|
||||
@@ -377,6 +381,7 @@ exports[`components/Switch disabled when "true" a disabled checkbox is rendered
|
||||
"msFlexItemAlign": "start",
|
||||
"msFlexNegative": 0,
|
||||
"msPreferredSize": "auto",
|
||||
"msTransform": "translateX(0%)",
|
||||
"paddingBottom": "0px",
|
||||
"paddingLeft": "0px",
|
||||
"paddingRight": "0px",
|
||||
@@ -384,7 +389,8 @@ exports[`components/Switch disabled when "true" a disabled checkbox is rendered
|
||||
"position": "relative",
|
||||
"textAlign": "inherit",
|
||||
"textDecoration": "none",
|
||||
"transition": "background-color 0.1s",
|
||||
"transform": "translateX(0%)",
|
||||
"transition": "0.1s",
|
||||
"width": "20px",
|
||||
}
|
||||
} />
|
||||
@@ -489,7 +495,7 @@ exports[`components/Switch value when "false" an unchecked checkbox is rendered
|
||||
"WebkitFlexBasis": "auto",
|
||||
"WebkitFlexDirection": "column",
|
||||
"WebkitFlexShrink": 0,
|
||||
"WebkitTransition": "background-color 0.1s",
|
||||
"WebkitTransition": "0.1s",
|
||||
"alignItems": "stretch",
|
||||
"backgroundColor": "#939393",
|
||||
"borderBottomLeftRadius": "10px",
|
||||
@@ -534,7 +540,7 @@ exports[`components/Switch value when "false" an unchecked checkbox is rendered
|
||||
"textAlign": "inherit",
|
||||
"textDecoration": "none",
|
||||
"top": "0px",
|
||||
"transition": "background-color 0.1s",
|
||||
"transition": "0.1s",
|
||||
"width": "90%",
|
||||
}
|
||||
} />
|
||||
@@ -551,7 +557,8 @@ exports[`components/Switch value when "false" an unchecked checkbox is rendered
|
||||
"WebkitFlexBasis": "auto",
|
||||
"WebkitFlexDirection": "column",
|
||||
"WebkitFlexShrink": 0,
|
||||
"WebkitTransition": "background-color 0.1s",
|
||||
"WebkitTransform": "translateX(0%)",
|
||||
"WebkitTransition": "0.1s",
|
||||
"alignItems": "stretch",
|
||||
"alignSelf": "flex-start",
|
||||
"backgroundColor": "#FAFAFA",
|
||||
@@ -588,6 +595,7 @@ exports[`components/Switch value when "false" an unchecked checkbox is rendered
|
||||
"msFlexItemAlign": "start",
|
||||
"msFlexNegative": 0,
|
||||
"msPreferredSize": "auto",
|
||||
"msTransform": "translateX(0%)",
|
||||
"paddingBottom": "0px",
|
||||
"paddingLeft": "0px",
|
||||
"paddingRight": "0px",
|
||||
@@ -595,7 +603,8 @@ exports[`components/Switch value when "false" an unchecked checkbox is rendered
|
||||
"position": "relative",
|
||||
"textAlign": "inherit",
|
||||
"textDecoration": "none",
|
||||
"transition": "background-color 0.1s",
|
||||
"transform": "translateX(0%)",
|
||||
"transition": "0.1s",
|
||||
"width": "20px",
|
||||
}
|
||||
} />
|
||||
@@ -700,7 +709,7 @@ exports[`components/Switch value when "true" a checked checkbox is rendered 1`]
|
||||
"WebkitFlexBasis": "auto",
|
||||
"WebkitFlexDirection": "column",
|
||||
"WebkitFlexShrink": 0,
|
||||
"WebkitTransition": "background-color 0.1s",
|
||||
"WebkitTransition": "0.1s",
|
||||
"alignItems": "stretch",
|
||||
"backgroundColor": "#A3D3CF",
|
||||
"borderBottomLeftRadius": "10px",
|
||||
@@ -745,7 +754,7 @@ exports[`components/Switch value when "true" a checked checkbox is rendered 1`]
|
||||
"textAlign": "inherit",
|
||||
"textDecoration": "none",
|
||||
"top": "0px",
|
||||
"transition": "background-color 0.1s",
|
||||
"transition": "0.1s",
|
||||
"width": "90%",
|
||||
}
|
||||
} />
|
||||
@@ -755,16 +764,17 @@ exports[`components/Switch value when "true" a checked checkbox is rendered 1`]
|
||||
Object {
|
||||
"MozBoxSizing": "border-box",
|
||||
"WebkitAlignItems": "stretch",
|
||||
"WebkitAlignSelf": "flex-end",
|
||||
"WebkitAlignSelf": "flex-start",
|
||||
"WebkitBoxAlign": "stretch",
|
||||
"WebkitBoxDirection": "normal",
|
||||
"WebkitBoxOrient": "vertical",
|
||||
"WebkitFlexBasis": "auto",
|
||||
"WebkitFlexDirection": "column",
|
||||
"WebkitFlexShrink": 0,
|
||||
"WebkitTransition": "background-color 0.1s",
|
||||
"WebkitTransform": "translateX(100%)",
|
||||
"WebkitTransition": "0.1s",
|
||||
"alignItems": "stretch",
|
||||
"alignSelf": "flex-end",
|
||||
"alignSelf": "flex-start",
|
||||
"backgroundColor": "#009688",
|
||||
"borderBottomLeftRadius": "100%",
|
||||
"borderBottomRightRadius": "100%",
|
||||
@@ -796,9 +806,10 @@ exports[`components/Switch value when "true" a checked checkbox is rendered 1`]
|
||||
"minWidth": "0px",
|
||||
"msFlexAlign": "stretch",
|
||||
"msFlexDirection": "column",
|
||||
"msFlexItemAlign": "end",
|
||||
"msFlexItemAlign": "start",
|
||||
"msFlexNegative": 0,
|
||||
"msPreferredSize": "auto",
|
||||
"msTransform": "translateX(100%)",
|
||||
"paddingBottom": "0px",
|
||||
"paddingLeft": "0px",
|
||||
"paddingRight": "0px",
|
||||
@@ -806,7 +817,8 @@ exports[`components/Switch value when "true" a checked checkbox is rendered 1`]
|
||||
"position": "relative",
|
||||
"textAlign": "inherit",
|
||||
"textDecoration": "none",
|
||||
"transition": "background-color 0.1s",
|
||||
"transform": "translateX(100%)",
|
||||
"transition": "0.1s",
|
||||
"width": "20px",
|
||||
}
|
||||
} />
|
||||
|
||||
@@ -88,9 +88,9 @@ class Switch extends Component {
|
||||
const thumbStyle = [
|
||||
styles.thumb,
|
||||
{
|
||||
alignSelf: value ? 'flex-end' : 'flex-start',
|
||||
backgroundColor: thumbCurrentColor,
|
||||
height: thumbHeight,
|
||||
transform: [ { translateX: value ? '100%' : '0%' } ],
|
||||
width: thumbWidth
|
||||
},
|
||||
disabled && styles.disabledThumb
|
||||
@@ -151,16 +151,17 @@ const styles = StyleSheet.create({
|
||||
...StyleSheet.absoluteFillObject,
|
||||
height: '70%',
|
||||
margin: 'auto',
|
||||
transition: 'background-color 0.1s',
|
||||
transition: '0.1s',
|
||||
width: '90%'
|
||||
},
|
||||
disabledTrack: {
|
||||
backgroundColor: '#D5D5D5'
|
||||
},
|
||||
thumb: {
|
||||
alignSelf: 'flex-start',
|
||||
borderRadius: '100%',
|
||||
boxShadow: thumbDefaultBoxShadow,
|
||||
transition: 'background-color 0.1s'
|
||||
transition: '0.1s'
|
||||
},
|
||||
disabledThumb: {
|
||||
backgroundColor: '#BDBDBD'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import TextPropTypes from '../../propTypes/TextPropTypes';
|
||||
import ViewStylePropTypes from '../View/ViewStylePropTypes';
|
||||
|
||||
module.exports = {
|
||||
module.exports = process.env.NODE_ENV !== 'production' ? {
|
||||
...ViewStylePropTypes,
|
||||
...TextPropTypes
|
||||
};
|
||||
} : {};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
exports[`components/Text prop "children" 1`] = `
|
||||
<span
|
||||
className=""
|
||||
onClick={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"borderBottomWidth": "0px",
|
||||
@@ -27,10 +26,39 @@ exports[`components/Text prop "children" 1`] = `
|
||||
</span>
|
||||
`;
|
||||
|
||||
exports[`components/Text prop "selectable" 1`] = `
|
||||
exports[`components/Text prop "onPress" 1`] = `
|
||||
<span
|
||||
className=""
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"borderBottomWidth": "0px",
|
||||
"borderLeftWidth": "0px",
|
||||
"borderRightWidth": "0px",
|
||||
"borderTopWidth": "0px",
|
||||
"color": "inherit",
|
||||
"cursor": "pointer",
|
||||
"display": "inline",
|
||||
"font": "inherit",
|
||||
"marginBottom": "0px",
|
||||
"marginLeft": "0px",
|
||||
"marginRight": "0px",
|
||||
"marginTop": "0px",
|
||||
"paddingBottom": "0px",
|
||||
"paddingLeft": "0px",
|
||||
"paddingRight": "0px",
|
||||
"paddingTop": "0px",
|
||||
"textDecoration": "none",
|
||||
"wordWrap": "break-word",
|
||||
}
|
||||
}
|
||||
tabIndex={0} />
|
||||
`;
|
||||
|
||||
exports[`components/Text prop "selectable" 1`] = `
|
||||
<span
|
||||
className=""
|
||||
style={
|
||||
Object {
|
||||
"borderBottomWidth": "0px",
|
||||
@@ -57,7 +85,6 @@ exports[`components/Text prop "selectable" 1`] = `
|
||||
exports[`components/Text prop "selectable" 2`] = `
|
||||
<span
|
||||
className=""
|
||||
onClick={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"MozUserSelect": "none",
|
||||
|
||||
@@ -14,6 +14,12 @@ describe('components/Text', () => {
|
||||
|
||||
test('prop "numberOfLines"');
|
||||
|
||||
test('prop "onPress"', () => {
|
||||
const onPress = (e) => {};
|
||||
const component = renderer.create(<Text onPress={onPress} />);
|
||||
expect(component.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('prop "selectable"', () => {
|
||||
let component = renderer.create(<Text />);
|
||||
expect(component.toJSON()).toMatchSnapshot();
|
||||
|
||||
@@ -29,27 +29,44 @@ class Text extends Component {
|
||||
render() {
|
||||
const {
|
||||
numberOfLines,
|
||||
onLayout, // eslint-disable-line
|
||||
onPress, // eslint-disable-line
|
||||
onPress,
|
||||
selectable,
|
||||
style,
|
||||
/* eslint-disable */
|
||||
adjustsFontSizeToFit,
|
||||
allowFontScaling,
|
||||
ellipsizeMode,
|
||||
minimumFontScale,
|
||||
onLayout,
|
||||
suppressHighlighting,
|
||||
/* eslint-enable */
|
||||
...other
|
||||
} = this.props;
|
||||
|
||||
if (onPress) {
|
||||
other.onClick = onPress;
|
||||
other.onKeyDown = this._createEnterHandler(onPress);
|
||||
other.tabIndex = 0;
|
||||
}
|
||||
|
||||
return createDOMElement('span', {
|
||||
...other,
|
||||
onClick: this._onPress,
|
||||
style: [
|
||||
styles.initial,
|
||||
style,
|
||||
!selectable && styles.notSelectable,
|
||||
numberOfLines === 1 && styles.singleLineStyle
|
||||
numberOfLines === 1 && styles.singleLineStyle,
|
||||
onPress && styles.pressable
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
_onPress = (e) => {
|
||||
if (this.props.onPress) { this.props.onPress(e); }
|
||||
_createEnterHandler(fn) {
|
||||
return (e) => {
|
||||
if (e.keyCode === 13) {
|
||||
fn && fn(e);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +84,9 @@ const styles = StyleSheet.create({
|
||||
notSelectable: {
|
||||
userSelect: 'none'
|
||||
},
|
||||
pressable: {
|
||||
cursor: 'pointer'
|
||||
},
|
||||
singleLineStyle: {
|
||||
maxWidth: '100%',
|
||||
overflow: 'hidden',
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
/* eslint-env jasmine, jest */
|
||||
|
||||
import React from 'react';
|
||||
import StyleSheet from '../../../apis/StyleSheet';
|
||||
import TextareaAutosize from 'react-textarea-autosize';
|
||||
import TextInput from '..';
|
||||
import { mount, shallow } from 'enzyme';
|
||||
|
||||
const placeholderText = 'placeholderText';
|
||||
const findNativeInput = (wrapper) => wrapper.find('input');
|
||||
const findNativeTextarea = (wrapper) => wrapper.find(TextareaAutosize);
|
||||
const findPlaceholder = (wrapper) => wrapper.find({ children: placeholderText });
|
||||
|
||||
const testIfDocumentIsFocused = (message, fn) => {
|
||||
if (document.hasFocus && document.hasFocus()) {
|
||||
@@ -184,24 +181,6 @@ describe('components/TextInput', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('prop "placeholder"', () => {
|
||||
let textInput = shallow(<TextInput />);
|
||||
expect(findPlaceholder(textInput).length).toEqual(0);
|
||||
|
||||
textInput = shallow(<TextInput placeholder={placeholderText} />);
|
||||
expect(findPlaceholder(textInput).length).toEqual(1);
|
||||
});
|
||||
|
||||
test('prop "placeholderTextColor"', () => {
|
||||
let placeholderElement = findPlaceholder(shallow(<TextInput placeholder={placeholderText} />));
|
||||
expect(StyleSheet.flatten(placeholderElement.prop('style')).color).toEqual('darkgray');
|
||||
|
||||
placeholderElement = findPlaceholder(
|
||||
shallow(<TextInput placeholder={placeholderText} placeholderTextColor='red' />)
|
||||
);
|
||||
expect(StyleSheet.flatten(placeholderElement.prop('style')).color).toEqual('red');
|
||||
});
|
||||
|
||||
test('prop "secureTextEntry"', () => {
|
||||
let input = findNativeInput(shallow(<TextInput secureTextEntry />));
|
||||
expect(input.prop('type')).toEqual('password');
|
||||
@@ -224,20 +203,6 @@ describe('components/TextInput', () => {
|
||||
// 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);
|
||||
const borderWidth = StyleSheet.flatten(textInput.prop('style')).borderWidth;
|
||||
expect(borderWidth).toEqual(1);
|
||||
expect(input.prop('style').textAlign).toEqual('center');
|
||||
});
|
||||
|
||||
test('prop "value"', () => {
|
||||
const value = 'value';
|
||||
const input = findNativeInput(shallow(<TextInput value={value} />));
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
import applyLayout from '../../modules/applyLayout';
|
||||
import applyNativeMethods from '../../modules/applyNativeMethods';
|
||||
import createDOMElement from '../../modules/createDOMElement';
|
||||
import findNodeHandle from '../../modules/findNodeHandle';
|
||||
import omit from 'lodash/omit';
|
||||
import pick from 'lodash/pick';
|
||||
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';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
|
||||
const viewStyleProps = Object.keys(ViewStylePropTypes);
|
||||
import { Component, PropTypes } from 'react';
|
||||
|
||||
/**
|
||||
* React Native events differ from W3C events.
|
||||
@@ -25,7 +21,7 @@ const normalizeEventHandler = (handler) => (e) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Determins whether a 'selection' prop differs from a node's existing
|
||||
* Determines whether a 'selection' prop differs from a node's existing
|
||||
* selection state.
|
||||
*/
|
||||
const isSelectionStale = (node, selection) => {
|
||||
@@ -64,7 +60,7 @@ class TextInput extends Component {
|
||||
defaultValue: PropTypes.string,
|
||||
editable: PropTypes.bool,
|
||||
keyboardType: PropTypes.oneOf([
|
||||
'default', 'email-address', 'numeric', 'phone-pad', 'search', 'url', 'web-search'
|
||||
'default', 'email-address', 'number-pad', 'numeric', 'phone-pad', 'search', 'url', 'web-search'
|
||||
]),
|
||||
maxLength: PropTypes.number,
|
||||
maxNumberOfLines: PropTypes.number,
|
||||
@@ -85,7 +81,6 @@ class TextInput extends Component {
|
||||
end: PropTypes.number
|
||||
}),
|
||||
style: Text.propTypes.style,
|
||||
testID: Text.propTypes.testID,
|
||||
value: PropTypes.string
|
||||
};
|
||||
|
||||
@@ -101,60 +96,63 @@ class TextInput extends Component {
|
||||
style: {}
|
||||
};
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.state = { showPlaceholder: !props.value && !props.defaultValue };
|
||||
}
|
||||
|
||||
blur() {
|
||||
TextInputState.blurTextInput(findNodeHandle(this._inputRef));
|
||||
TextInputState.blurTextInput(this._node);
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.setNativeProps({ text: '' });
|
||||
this._node.value = '';
|
||||
}
|
||||
|
||||
focus() {
|
||||
TextInputState.focusTextInput(findNodeHandle(this._inputRef));
|
||||
TextInputState.focusTextInput(this._node);
|
||||
}
|
||||
|
||||
isFocused() {
|
||||
return TextInputState.currentlyFocusedField() === findNodeHandle(this._inputRef);
|
||||
return TextInputState.currentlyFocusedField() === this._node;
|
||||
}
|
||||
|
||||
setNativeProps(props) {
|
||||
UIManager.updateView(this._inputRef, props, this);
|
||||
UIManager.updateView(this._node, props, this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
setSelection(findNodeHandle(this._inputRef), this.props.selection);
|
||||
setSelection(this._node, this.props.selection);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
setSelection(findNodeHandle(this._inputRef), this.props.selection);
|
||||
setSelection(this._node, this.props.selection);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
accessibilityLabel, // eslint-disable-line
|
||||
autoCapitalize,
|
||||
autoComplete,
|
||||
autoCorrect,
|
||||
autoFocus,
|
||||
defaultValue,
|
||||
editable,
|
||||
keyboardType,
|
||||
maxLength,
|
||||
maxNumberOfLines,
|
||||
multiline,
|
||||
numberOfLines,
|
||||
onLayout,
|
||||
placeholder,
|
||||
placeholderTextColor,
|
||||
secureTextEntry,
|
||||
style,
|
||||
testID,
|
||||
value
|
||||
/* eslint-disable */
|
||||
blurOnSubmit,
|
||||
clearTextOnFocus,
|
||||
dataDetectorTypes,
|
||||
enablesReturnKeyAutomatically,
|
||||
keyboardAppearance,
|
||||
onChangeText,
|
||||
onContentSizeChange,
|
||||
onEndEditing,
|
||||
onLayout,
|
||||
onSelectionChange,
|
||||
onSubmitEditing,
|
||||
placeholderTextColor,
|
||||
returnKeyType,
|
||||
selection,
|
||||
selectionColor,
|
||||
selectTextOnFocus,
|
||||
/* eslint-enable */
|
||||
...other
|
||||
} = this.props;
|
||||
|
||||
let type;
|
||||
@@ -163,6 +161,7 @@ class TextInput extends Component {
|
||||
case 'email-address':
|
||||
type = 'email';
|
||||
break;
|
||||
case 'number-pad':
|
||||
case 'numeric':
|
||||
type = 'number';
|
||||
break;
|
||||
@@ -184,29 +183,22 @@ class TextInput extends Component {
|
||||
type = 'password';
|
||||
}
|
||||
|
||||
// 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 flattenedStyle = StyleSheet.flatten(style);
|
||||
const rootStyles = pick(flattenedStyle, viewStyleProps);
|
||||
const textStyles = omit(flattenedStyle, viewStyleProps);
|
||||
const component = multiline ? TextareaAutosize : 'input';
|
||||
|
||||
const props = {
|
||||
autoCapitalize,
|
||||
autoComplete,
|
||||
...other,
|
||||
autoCorrect: autoCorrect ? 'on' : 'off',
|
||||
autoFocus,
|
||||
defaultValue,
|
||||
maxLength,
|
||||
onBlur: normalizeEventHandler(this._handleBlur),
|
||||
onChange: normalizeEventHandler(this._handleChange),
|
||||
onFocus: normalizeEventHandler(this._handleFocus),
|
||||
onKeyPress: normalizeEventHandler(this._handleKeyPress),
|
||||
onSelect: normalizeEventHandler(this._handleSelectionChange),
|
||||
readOnly: !editable,
|
||||
ref: this._setInputRef,
|
||||
style: [ styles.input, textStyles, { outline: style.outline } ],
|
||||
value
|
||||
ref: this._setNode,
|
||||
style: [
|
||||
styles.initial,
|
||||
style
|
||||
]
|
||||
};
|
||||
|
||||
if (multiline) {
|
||||
@@ -216,66 +208,27 @@ class TextInput extends Component {
|
||||
props.type = type;
|
||||
}
|
||||
|
||||
const component = multiline ? TextareaAutosize : 'input';
|
||||
|
||||
const optionalPlaceholder = placeholder && this.state.showPlaceholder && (
|
||||
<View pointerEvents='none' style={styles.placeholder}>
|
||||
<Text
|
||||
children={placeholder}
|
||||
style={[
|
||||
styles.placeholderText,
|
||||
textStyles,
|
||||
placeholderTextColor && { color: placeholderTextColor }
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
||||
return (
|
||||
<View
|
||||
accessibilityLabel={accessibilityLabel}
|
||||
onClick={this._handleClick}
|
||||
onLayout={onLayout}
|
||||
style={[ styles.initial, rootStyles ]}
|
||||
testID={testID}
|
||||
>
|
||||
<View style={styles.wrapper}>
|
||||
{createDOMElement(component, props)}
|
||||
{optionalPlaceholder}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
return createDOMElement(component, props);
|
||||
}
|
||||
|
||||
_handleBlur = (e) => {
|
||||
const { onBlur } = this.props;
|
||||
const { text } = e.nativeEvent;
|
||||
this.setState({ showPlaceholder: text === '' });
|
||||
if (onBlur) { onBlur(e); }
|
||||
}
|
||||
|
||||
_handleChange = (e) => {
|
||||
const { onChange, onChangeText } = this.props;
|
||||
const { text } = e.nativeEvent;
|
||||
this.setState({ showPlaceholder: text === '' });
|
||||
if (onChange) { onChange(e); }
|
||||
if (onChangeText) { onChangeText(text); }
|
||||
}
|
||||
|
||||
_handleClick = (e) => {
|
||||
if (this.props.editable) {
|
||||
this.focus();
|
||||
}
|
||||
}
|
||||
|
||||
_handleFocus = (e) => {
|
||||
const { clearTextOnFocus, onFocus, selectTextOnFocus } = this.props;
|
||||
const { text } = e.nativeEvent;
|
||||
const node = findNodeHandle(this._inputRef);
|
||||
const node = this._node;
|
||||
if (onFocus) { onFocus(e); }
|
||||
if (clearTextOnFocus) { this.clear(); }
|
||||
if (selectTextOnFocus) { node && node.select(); }
|
||||
this.setState({ showPlaceholder: text === '' });
|
||||
}
|
||||
|
||||
_handleKeyPress = (e) => {
|
||||
@@ -303,43 +256,23 @@ class TextInput extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
_setInputRef = (component) => {
|
||||
this._inputRef = component;
|
||||
_setNode = (component) => {
|
||||
this._node = findNodeHandle(component);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
initial: {
|
||||
borderColor: 'black'
|
||||
},
|
||||
wrapper: {
|
||||
flex: 1
|
||||
},
|
||||
input: {
|
||||
appearance: 'none',
|
||||
backgroundColor: 'transparent',
|
||||
borderColor: 'black',
|
||||
borderRadius: 0,
|
||||
borderWidth: 0,
|
||||
boxSizing: 'border-box',
|
||||
color: 'inherit',
|
||||
flex: 1,
|
||||
font: 'inherit',
|
||||
minHeight: '100%', // center small inputs (fix #139)
|
||||
padding: 0,
|
||||
zIndex: 1
|
||||
},
|
||||
placeholder: {
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
top: 0
|
||||
},
|
||||
placeholderText: {
|
||||
color: 'darkgray',
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'pre'
|
||||
padding: 0
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = applyNativeMethods(TextInput);
|
||||
module.exports = applyLayout(applyNativeMethods(TextInput));
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
/* @edit start */
|
||||
const BoundingDimensions = require('./BoundingDimensions');
|
||||
const keyMirror = require('fbjs/lib/keyMirror');
|
||||
const normalizeColor = require('../../modules/normalizeColor');
|
||||
const Position = require('./Position');
|
||||
const React = require('react');
|
||||
@@ -111,16 +110,16 @@ const View = require('../../components/View');
|
||||
/**
|
||||
* Touchable states.
|
||||
*/
|
||||
var States = keyMirror({
|
||||
NOT_RESPONDER: null, // Not the responder
|
||||
RESPONDER_INACTIVE_PRESS_IN: null, // Responder, inactive, in the `PressRect`
|
||||
RESPONDER_INACTIVE_PRESS_OUT: null, // Responder, inactive, out of `PressRect`
|
||||
RESPONDER_ACTIVE_PRESS_IN: null, // Responder, active, in the `PressRect`
|
||||
RESPONDER_ACTIVE_PRESS_OUT: null, // Responder, active, out of `PressRect`
|
||||
RESPONDER_ACTIVE_LONG_PRESS_IN: null, // Responder, active, in the `PressRect`, after long press threshold
|
||||
RESPONDER_ACTIVE_LONG_PRESS_OUT: null, // Responder, active, out of `PressRect`, after long press threshold
|
||||
ERROR: null
|
||||
});
|
||||
var States = {
|
||||
NOT_RESPONDER: 'NOT_RESPONDER', // Not the responder
|
||||
RESPONDER_INACTIVE_PRESS_IN: 'RESPONDER_INACTIVE_PRESS_IN', // Responder, inactive, in the `PressRect`
|
||||
RESPONDER_INACTIVE_PRESS_OUT: 'RESPONDER_INACTIVE_PRESS_OUT', // Responder, inactive, out of `PressRect`
|
||||
RESPONDER_ACTIVE_PRESS_IN: 'RESPONDER_ACTIVE_PRESS_IN', // Responder, active, in the `PressRect`
|
||||
RESPONDER_ACTIVE_PRESS_OUT: 'RESPONDER_ACTIVE_PRESS_OUT', // Responder, active, out of `PressRect`
|
||||
RESPONDER_ACTIVE_LONG_PRESS_IN: 'RESPONDER_ACTIVE_LONG_PRESS_IN', // Responder, active, in the `PressRect`, after long press threshold
|
||||
RESPONDER_ACTIVE_LONG_PRESS_OUT: 'RESPONDER_ACTIVE_LONG_PRESS_OUT', // Responder, active, out of `PressRect`, after long press threshold
|
||||
ERROR: 'ERROR'
|
||||
};
|
||||
|
||||
/**
|
||||
* Quick lookup map for states that are considered to be "active"
|
||||
@@ -147,15 +146,15 @@ var IsLongPressingIn = {
|
||||
/**
|
||||
* Inputs to the state machine.
|
||||
*/
|
||||
var Signals = keyMirror({
|
||||
DELAY: null,
|
||||
RESPONDER_GRANT: null,
|
||||
RESPONDER_RELEASE: null,
|
||||
RESPONDER_TERMINATED: null,
|
||||
ENTER_PRESS_RECT: null,
|
||||
LEAVE_PRESS_RECT: null,
|
||||
LONG_PRESS_DETECTED: null,
|
||||
});
|
||||
var Signals = {
|
||||
DELAY: 'DELAY',
|
||||
RESPONDER_GRANT: 'RESPONDER_GRANT',
|
||||
RESPONDER_RELEASE: 'RESPONDER_RELEASE',
|
||||
RESPONDER_TERMINATED: 'RESPONDER_TERMINATED',
|
||||
ENTER_PRESS_RECT: 'ENTER_PRESS_RECT',
|
||||
LEAVE_PRESS_RECT: 'LEAVE_PRESS_RECT',
|
||||
LONG_PRESS_DETECTED: 'LONG_PRESS_DETECTED',
|
||||
};
|
||||
|
||||
/**
|
||||
* Mapping from States x Signals => States
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
/* 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 TouchableBounce
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var Animated = require('../../apis/Animated');
|
||||
var EdgeInsetsPropType = require('../../propTypes/EdgeInsetsPropType');
|
||||
var NativeMethodsMixin = require('../../modules/NativeMethodsMixin');
|
||||
var React = require('react');
|
||||
var StyleSheet = require('../../apis/StyleSheet');
|
||||
var Touchable = require('./Touchable');
|
||||
|
||||
type Event = Object;
|
||||
|
||||
type State = {
|
||||
animationID: ?number;
|
||||
scale: Animated.Value;
|
||||
};
|
||||
|
||||
var PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30};
|
||||
|
||||
/**
|
||||
* Example of using the `TouchableMixin` to play well with other responder
|
||||
* locking views including `ScrollView`. `TouchableMixin` provides touchable
|
||||
* hooks (`this.touchableHandle*`) that we forward events to. In turn,
|
||||
* `TouchableMixin` expects us to implement some abstract methods to handle
|
||||
* interesting interactions such as `handleTouchablePress`.
|
||||
*/
|
||||
var TouchableBounce = React.createClass({
|
||||
mixins: [Touchable.Mixin, NativeMethodsMixin],
|
||||
|
||||
propTypes: {
|
||||
onPress: React.PropTypes.func,
|
||||
onPressIn: React.PropTypes.func,
|
||||
onPressOut: React.PropTypes.func,
|
||||
// The function passed takes a callback to start the animation which should
|
||||
// be run after this onPress handler is done. You can use this (for example)
|
||||
// to update UI before starting the animation.
|
||||
onPressWithCompletion: React.PropTypes.func,
|
||||
// the function passed is called after the animation is complete
|
||||
onPressAnimationComplete: React.PropTypes.func,
|
||||
/**
|
||||
* When the scroll view is disabled, this defines how far your touch may
|
||||
* move off of the button, before deactivating the button. Once deactivated,
|
||||
* try moving it back and you'll see that the button is once again
|
||||
* reactivated! Move it back and forth several times while the scroll view
|
||||
* is disabled. Ensure you pass in a constant to reduce memory allocations.
|
||||
*/
|
||||
pressRetentionOffset: EdgeInsetsPropType,
|
||||
/**
|
||||
* This defines how far your touch can start away from the button. This is
|
||||
* added to `pressRetentionOffset` when moving off of the button.
|
||||
* ** NOTE **
|
||||
* The touch area never extends past the parent view bounds and the Z-index
|
||||
* of sibling views always takes precedence if a touch hits two overlapping
|
||||
* views.
|
||||
*/
|
||||
hitSlop: EdgeInsetsPropType,
|
||||
},
|
||||
|
||||
getInitialState: function(): State {
|
||||
return {
|
||||
...this.touchableGetInitialState(),
|
||||
scale: new Animated.Value(1),
|
||||
};
|
||||
},
|
||||
|
||||
bounceTo: function(
|
||||
value: number,
|
||||
velocity: number,
|
||||
bounciness: number,
|
||||
callback?: ?Function
|
||||
) {
|
||||
Animated.spring(this.state.scale, {
|
||||
toValue: value,
|
||||
velocity,
|
||||
bounciness,
|
||||
}).start(callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* `Touchable.Mixin` self callbacks. The mixin will invoke these if they are
|
||||
* defined on your component.
|
||||
*/
|
||||
touchableHandleActivePressIn: function(e: Event) {
|
||||
this.bounceTo(0.93, 0.1, 0);
|
||||
this.props.onPressIn && this.props.onPressIn(e);
|
||||
},
|
||||
|
||||
touchableHandleActivePressOut: function(e: Event) {
|
||||
this.bounceTo(1, 0.4, 0);
|
||||
this.props.onPressOut && this.props.onPressOut(e);
|
||||
},
|
||||
|
||||
touchableHandlePress: function(e: Event) {
|
||||
var onPressWithCompletion = this.props.onPressWithCompletion;
|
||||
if (onPressWithCompletion) {
|
||||
onPressWithCompletion(() => {
|
||||
this.state.scale.setValue(0.93);
|
||||
this.bounceTo(1, 10, 10, this.props.onPressAnimationComplete);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.bounceTo(1, 10, 10, this.props.onPressAnimationComplete);
|
||||
this.props.onPress && this.props.onPress(e);
|
||||
},
|
||||
|
||||
touchableGetPressRectOffset: function(): typeof PRESS_RETENTION_OFFSET {
|
||||
return this.props.pressRetentionOffset || PRESS_RETENTION_OFFSET;
|
||||
},
|
||||
|
||||
touchableGetHitSlop: function(): ?Object {
|
||||
return this.props.hitSlop;
|
||||
},
|
||||
|
||||
touchableGetHighlightDelayMS: function(): number {
|
||||
return 0;
|
||||
},
|
||||
|
||||
render: function(): ReactElement {
|
||||
const scaleTransform = [{ scale: this.state.scale }];
|
||||
const propsTransform = this.props.style.transform;
|
||||
const transform = propsTransform && Array.isArray(propsTransform) ? propsTransform.concat(scaleTransform) : scaleTransform;
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
accessible={true}
|
||||
accessibilityLabel={this.props.accessibilityLabel}
|
||||
accessibilityRole={this.props.accessibilityRole || 'button'}
|
||||
testID={this.props.testID}
|
||||
hitSlop={this.props.hitSlop}
|
||||
onStartShouldSetResponder={this.touchableHandleStartShouldSetResponder}
|
||||
onResponderTerminationRequest={this.touchableHandleResponderTerminationRequest}
|
||||
onResponderGrant={this.touchableHandleResponderGrant}
|
||||
onResponderMove={this.touchableHandleResponderMove}
|
||||
onResponderRelease={this.touchableHandleResponderRelease}
|
||||
onResponderTerminate={this.touchableHandleResponderTerminate}
|
||||
style={[styles.root, this.props.style, { transform }]}
|
||||
tabIndex='0'
|
||||
>
|
||||
{this.props.children}
|
||||
</Animated.View>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
cursor: 'pointer',
|
||||
userSelect: 'none'
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = TouchableBounce;
|
||||
@@ -18,15 +18,16 @@ var ColorPropType = require('../../propTypes/ColorPropType');
|
||||
var NativeMethodsMixin = require('../../modules/NativeMethodsMixin');
|
||||
var React = require('react');
|
||||
var StyleSheet = require('../../apis/StyleSheet');
|
||||
var StyleSheetPropType = require('../../propTypes/StyleSheetPropType');
|
||||
var TimerMixin = require('react-timer-mixin');
|
||||
var Touchable = require('./Touchable');
|
||||
var TouchableWithoutFeedback = require('./TouchableWithoutFeedback');
|
||||
var View = require('../View');
|
||||
var ViewStylePropTypes = require('../View/ViewStylePropTypes');
|
||||
|
||||
var ensureComponentIsNative = require('./ensureComponentIsNative');
|
||||
var ensurePositiveDelayProps = require('./ensurePositiveDelayProps');
|
||||
var keyOf = require('fbjs/lib/keyOf');
|
||||
var merge = require('../../modules/merge');
|
||||
|
||||
type Event = Object;
|
||||
|
||||
@@ -78,7 +79,7 @@ var TouchableHighlight = React.createClass({
|
||||
* active.
|
||||
*/
|
||||
underlayColor: ColorPropType,
|
||||
style: View.propTypes.style,
|
||||
style: StyleSheetPropType(ViewStylePropTypes),
|
||||
/**
|
||||
* Called immediately after the underlay is shown
|
||||
*/
|
||||
@@ -115,7 +116,7 @@ var TouchableHighlight = React.createClass({
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return merge(this.touchableGetInitialState(), this.computeSyntheticState(this.props))
|
||||
return { ...this.touchableGetInitialState(), ...this.computeSyntheticState(this.props) }
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
|
||||
@@ -14,13 +14,13 @@
|
||||
|
||||
// Note (avik): add @flow when Flow supports spread properties in propTypes
|
||||
|
||||
var Animated = require('../../apis/Animated');
|
||||
var NativeMethodsMixin = require('../../modules/NativeMethodsMixin');
|
||||
var React = require('react');
|
||||
var StyleSheet = require('../../apis/StyleSheet');
|
||||
var TimerMixin = require('react-timer-mixin');
|
||||
var Touchable = require('./Touchable');
|
||||
var TouchableWithoutFeedback = require('./TouchableWithoutFeedback');
|
||||
var View = require('../View');
|
||||
|
||||
var ensurePositiveDelayProps = require('./ensurePositiveDelayProps');
|
||||
var flattenStyle = StyleSheet.flatten
|
||||
@@ -70,10 +70,7 @@ var TouchableOpacity = React.createClass({
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
...this.touchableGetInitialState(),
|
||||
anim: new Animated.Value(1),
|
||||
};
|
||||
return this.touchableGetInitialState();
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
@@ -85,10 +82,11 @@ var TouchableOpacity = React.createClass({
|
||||
},
|
||||
|
||||
setOpacityTo: function(value) {
|
||||
Animated.timing(
|
||||
this.state.anim,
|
||||
{toValue: value, duration: 150}
|
||||
).start();
|
||||
this.setNativeProps({
|
||||
style: {
|
||||
opacity: value
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -166,7 +164,7 @@ var TouchableOpacity = React.createClass({
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<Animated.View
|
||||
<View
|
||||
accessible={this.props.accessible !== false}
|
||||
accessibilityLabel={this.props.accessibilityLabel}
|
||||
accessibilityRole={this.props.accessibilityRole}
|
||||
@@ -174,8 +172,7 @@ var TouchableOpacity = React.createClass({
|
||||
style={[
|
||||
styles.root,
|
||||
this.props.disabled && styles.disabled,
|
||||
this.props.style,
|
||||
{opacity: this.state.anim}
|
||||
this.props.style
|
||||
]}
|
||||
testID={this.props.testID}
|
||||
onLayout={this.props.onLayout}
|
||||
@@ -192,7 +189,7 @@ var TouchableOpacity = React.createClass({
|
||||
tabIndex={this.props.disabled ? null : '0'}
|
||||
>
|
||||
{this.props.children}
|
||||
</Animated.View>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
});
|
||||
@@ -200,6 +197,7 @@ var TouchableOpacity = React.createClass({
|
||||
var styles = StyleSheet.create({
|
||||
root: {
|
||||
cursor: 'pointer',
|
||||
transition: 'opacity 0.15s',
|
||||
userSelect: 'none'
|
||||
},
|
||||
disabled: {
|
||||
|
||||
@@ -16,7 +16,6 @@ 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');
|
||||
@@ -38,9 +37,9 @@ const TouchableWithoutFeedback = React.createClass({
|
||||
mixins: [TimerMixin, Touchable.Mixin],
|
||||
|
||||
propTypes: {
|
||||
accessible: View.propTypes.accessible,
|
||||
accessibilityLabel: View.propTypes.accessibilityLabel,
|
||||
accessibilityRole: View.propTypes.accessibilityRole,
|
||||
accessible: React.PropTypes.bool,
|
||||
accessibilityLabel: React.PropTypes.string,
|
||||
accessibilityRole: React.PropTypes.string,
|
||||
/**
|
||||
* If true, disable all interactions for this component.
|
||||
*/
|
||||
|
||||
@@ -8,7 +8,7 @@ const { number, oneOf, string } = PropTypes;
|
||||
const autoOrHiddenOrVisible = oneOf([ 'auto', 'hidden', 'visible' ]);
|
||||
const hiddenOrVisible = oneOf([ 'hidden', 'visible' ]);
|
||||
|
||||
module.exports = {
|
||||
module.exports = process.env.NODE_ENV !== 'production' ? {
|
||||
...BorderPropTypes,
|
||||
...LayoutPropTypes,
|
||||
...TransformPropTypes,
|
||||
@@ -36,4 +36,4 @@ module.exports = {
|
||||
userSelect: string,
|
||||
visibility: hiddenOrVisible,
|
||||
WebkitOverflowScrolling: oneOf([ 'auto', 'touch' ])
|
||||
};
|
||||
} : {};
|
||||
|
||||
@@ -87,11 +87,18 @@ class View extends Component {
|
||||
|
||||
render() {
|
||||
const {
|
||||
collapsable, // eslint-disable-line
|
||||
hitSlop, // eslint-disable-line
|
||||
onLayout, // eslint-disable-line
|
||||
pointerEvents,
|
||||
style,
|
||||
/* eslint-disable */
|
||||
accessibilityComponentType,
|
||||
accessibilityTraits,
|
||||
collapsable,
|
||||
hitSlop,
|
||||
onAccessibilityTap,
|
||||
onLayout,
|
||||
onMagicTap,
|
||||
removeClippedSubviews,
|
||||
/* eslint-enable */
|
||||
...other
|
||||
} = this.props;
|
||||
|
||||
|
||||
16
src/core.js
16
src/core.js
@@ -1,6 +1,8 @@
|
||||
import findNodeHandle from './modules/findNodeHandle';
|
||||
import ReactDOM from 'react-dom';
|
||||
import ReactDOMServer from 'react-dom/server';
|
||||
import ReactDefaultInjection from 'react/lib/ReactDefaultInjection';
|
||||
import { render, unmountComponentAtNode } from 'react/lib/ReactMount';
|
||||
|
||||
ReactDefaultInjection.inject();
|
||||
|
||||
// APIs
|
||||
import I18nManager from './apis/I18nManager';
|
||||
@@ -12,12 +14,14 @@ import Text from './components/Text';
|
||||
import TextInput from './components/TextInput';
|
||||
import View from './components/View';
|
||||
|
||||
// modules
|
||||
import createDOMElement from './modules/createDOMElement';
|
||||
|
||||
const ReactNativeCore = {
|
||||
createDOMElement,
|
||||
findNodeHandle,
|
||||
render: ReactDOM.render,
|
||||
renderToStaticMarkup: ReactDOMServer.renderToStaticMarkup,
|
||||
renderToString: ReactDOMServer.renderToString,
|
||||
unmountComponentAtNode: ReactDOM.unmountComponentAtNode,
|
||||
render,
|
||||
unmountComponentAtNode,
|
||||
// APIs
|
||||
I18nManager,
|
||||
StyleSheet,
|
||||
|
||||
24
src/index.js
24
src/index.js
@@ -1,16 +1,20 @@
|
||||
import findNodeHandle from './modules/findNodeHandle';
|
||||
import ReactDOM from 'react-dom';
|
||||
import ReactDOMServer from 'react-dom/server';
|
||||
import ReactDefaultInjection from 'react/lib/ReactDefaultInjection';
|
||||
import { render, unmountComponentAtNode } from 'react/lib/ReactMount';
|
||||
|
||||
ReactDefaultInjection.inject();
|
||||
|
||||
// APIs
|
||||
import Animated from './apis/Animated';
|
||||
import AppRegistry from './apis/AppRegistry';
|
||||
import AppState from './apis/AppState';
|
||||
import AsyncStorage from './apis/AsyncStorage';
|
||||
import Clipboard from './apis/Clipboard';
|
||||
import Dimensions from './apis/Dimensions';
|
||||
import Easing from 'animated/lib/Easing';
|
||||
import I18nManager from './apis/I18nManager';
|
||||
import InteractionManager from './apis/InteractionManager';
|
||||
import Linking from './apis/Linking';
|
||||
import NetInfo from './apis/NetInfo';
|
||||
import PanResponder from './apis/PanResponder';
|
||||
import PixelRatio from './apis/PixelRatio';
|
||||
@@ -21,6 +25,7 @@ import Vibration from './apis/Vibration';
|
||||
|
||||
// components
|
||||
import ActivityIndicator from './components/ActivityIndicator';
|
||||
import Button from './components/Button';
|
||||
import Image from './components/Image';
|
||||
import ListView from './components/ListView';
|
||||
import ProgressBar from './components/ProgressBar';
|
||||
@@ -29,17 +34,16 @@ import Switch from './components/Switch';
|
||||
import Text from './components/Text';
|
||||
import TextInput from './components/TextInput';
|
||||
import Touchable from './components/Touchable/Touchable';
|
||||
import TouchableBounce from './components/Touchable/TouchableBounce';
|
||||
import TouchableHighlight from './components/Touchable/TouchableHighlight';
|
||||
import TouchableOpacity from './components/Touchable/TouchableOpacity';
|
||||
import TouchableWithoutFeedback from './components/Touchable/TouchableWithoutFeedback';
|
||||
import View from './components/View';
|
||||
|
||||
// modules
|
||||
import createDOMElement from './modules/createDOMElement';
|
||||
import NativeModules from './modules/NativeModules';
|
||||
|
||||
// propTypes
|
||||
|
||||
import ColorPropType from './propTypes/ColorPropType';
|
||||
import EdgeInsetsPropType from './propTypes/EdgeInsetsPropType';
|
||||
import PointPropType from './propTypes/PointPropType';
|
||||
@@ -47,21 +51,20 @@ import PointPropType from './propTypes/PointPropType';
|
||||
const ReactNative = {
|
||||
// top-level API
|
||||
findNodeHandle,
|
||||
render: ReactDOM.render,
|
||||
unmountComponentAtNode: ReactDOM.unmountComponentAtNode,
|
||||
// web-only
|
||||
renderToStaticMarkup: ReactDOMServer.renderToStaticMarkup,
|
||||
renderToString: ReactDOMServer.renderToString,
|
||||
render,
|
||||
unmountComponentAtNode,
|
||||
|
||||
// APIs
|
||||
Animated,
|
||||
AppRegistry,
|
||||
AppState,
|
||||
AsyncStorage,
|
||||
Clipboard,
|
||||
Dimensions,
|
||||
Easing,
|
||||
I18nManager,
|
||||
InteractionManager,
|
||||
Linking,
|
||||
NetInfo,
|
||||
PanResponder,
|
||||
PixelRatio,
|
||||
@@ -72,6 +75,7 @@ const ReactNative = {
|
||||
|
||||
// components
|
||||
ActivityIndicator,
|
||||
Button,
|
||||
Image,
|
||||
ListView,
|
||||
ProgressBar,
|
||||
@@ -80,13 +84,13 @@ const ReactNative = {
|
||||
Text,
|
||||
TextInput,
|
||||
Touchable,
|
||||
TouchableBounce,
|
||||
TouchableHighlight,
|
||||
TouchableOpacity,
|
||||
TouchableWithoutFeedback,
|
||||
View,
|
||||
|
||||
// modules
|
||||
createDOMElement,
|
||||
NativeModules,
|
||||
|
||||
// propTypes
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import { Component } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import findNodeHandle from '../findNodeHandle';
|
||||
import UIManager from '../../apis/UIManager';
|
||||
|
||||
type MeasureInWindowOnSuccessCallback = (
|
||||
@@ -38,7 +38,7 @@ const NativeMethodsMixin = {
|
||||
* Removes focus from an input or view. This is the opposite of `focus()`.
|
||||
*/
|
||||
blur() {
|
||||
UIManager.blur(ReactDOM.findDOMNode(this));
|
||||
UIManager.blur(findNodeHandle(this));
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -46,7 +46,7 @@ const NativeMethodsMixin = {
|
||||
* The exact behavior triggered will depend the type of view.
|
||||
*/
|
||||
focus() {
|
||||
UIManager.focus(ReactDOM.findDOMNode(this));
|
||||
UIManager.focus(findNodeHandle(this));
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -54,7 +54,7 @@ const NativeMethodsMixin = {
|
||||
*/
|
||||
measure(callback: MeasureOnSuccessCallback) {
|
||||
UIManager.measure(
|
||||
ReactDOM.findDOMNode(this),
|
||||
findNodeHandle(this),
|
||||
mountSafeCallback(this, callback)
|
||||
);
|
||||
},
|
||||
@@ -76,7 +76,7 @@ const NativeMethodsMixin = {
|
||||
*/
|
||||
measureInWindow(callback: MeasureInWindowOnSuccessCallback) {
|
||||
UIManager.measureInWindow(
|
||||
ReactDOM.findDOMNode(this),
|
||||
findNodeHandle(this),
|
||||
mountSafeCallback(this, callback)
|
||||
);
|
||||
},
|
||||
@@ -90,7 +90,7 @@ const NativeMethodsMixin = {
|
||||
onFail: () => void /* currently unused */
|
||||
) {
|
||||
UIManager.measureLayout(
|
||||
ReactDOM.findDOMNode(this),
|
||||
findNodeHandle(this),
|
||||
relativeToNativeNode,
|
||||
mountSafeCallback(this, onFail),
|
||||
mountSafeCallback(this, onSuccess)
|
||||
@@ -102,7 +102,7 @@ const NativeMethodsMixin = {
|
||||
*/
|
||||
setNativeProps(nativeProps: Object) {
|
||||
UIManager.updateView(
|
||||
ReactDOM.findDOMNode(this),
|
||||
findNodeHandle(this),
|
||||
nativeProps,
|
||||
this
|
||||
);
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
'use strict';
|
||||
|
||||
var Dimensions = require('../../apis/Dimensions');
|
||||
var findNodeHandle = require('../findNodeHandle');
|
||||
var Platform = require('../../apis/Platform');
|
||||
var React = require('react');
|
||||
var ReactDOM = require('react-dom');
|
||||
// var Subscribable = require('../Subscribable');
|
||||
var TextInputState = require('../../components/TextInput/TextInputState');
|
||||
var UIManager = require('../../apis/UIManager');
|
||||
@@ -356,7 +356,7 @@ var ScrollResponderMixin = {
|
||||
scrollResponderGetScrollableNode: function(): any {
|
||||
return this.getScrollableNode ?
|
||||
this.getScrollableNode() :
|
||||
ReactDOM.findDOMNode(this);
|
||||
findNodeHandle(this);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -423,7 +423,7 @@ var ScrollResponderMixin = {
|
||||
this.preventNegativeScrollOffset = !!preventNegativeScrollOffset;
|
||||
UIManager.measureLayout(
|
||||
nodeHandle,
|
||||
ReactDOM.findDOMNode(this.getInnerViewNode()),
|
||||
findNodeHandle(this.getInnerViewNode()),
|
||||
this.scrollResponderTextInputFocusError,
|
||||
this.scrollResponderInputMeasureAndScrollToKeyboard
|
||||
);
|
||||
|
||||
@@ -12,13 +12,13 @@ const applyLayout = (Component) => {
|
||||
const componentDidUpdate = Component.prototype.componentDidUpdate || emptyFunction;
|
||||
|
||||
Component.prototype.componentDidMount = function () {
|
||||
componentDidMount();
|
||||
componentDidMount.call(this);
|
||||
this._layoutState = {};
|
||||
this._handleLayout();
|
||||
};
|
||||
|
||||
Component.prototype.componentDidUpdate = function () {
|
||||
componentDidUpdate();
|
||||
componentDidUpdate.call(this);
|
||||
this._handleLayout();
|
||||
};
|
||||
|
||||
|
||||
@@ -12,14 +12,7 @@ exports[`modules/createDOMElement prop "accessibilityLiveRegion" 1`] = `
|
||||
style={Object {}} />
|
||||
`;
|
||||
|
||||
exports[`modules/createDOMElement prop "accessibilityRole" 1`] = `
|
||||
<header
|
||||
className=""
|
||||
role="banner"
|
||||
style={Object {}} />
|
||||
`;
|
||||
|
||||
exports[`modules/createDOMElement prop "accessibilityRole" 2`] = `
|
||||
exports[`modules/createDOMElement prop "accessibilityRole" button 1`] = `
|
||||
<button
|
||||
className=""
|
||||
role="button"
|
||||
@@ -27,6 +20,22 @@ exports[`modules/createDOMElement prop "accessibilityRole" 2`] = `
|
||||
type="button" />
|
||||
`;
|
||||
|
||||
exports[`modules/createDOMElement prop "accessibilityRole" link and target="_blank" 1`] = `
|
||||
<a
|
||||
className=""
|
||||
rel=" noopener noreferrer"
|
||||
role="link"
|
||||
style={Object {}}
|
||||
target="_blank" />
|
||||
`;
|
||||
|
||||
exports[`modules/createDOMElement prop "accessibilityRole" roles 1`] = `
|
||||
<header
|
||||
className=""
|
||||
role="banner"
|
||||
style={Object {}} />
|
||||
`;
|
||||
|
||||
exports[`modules/createDOMElement prop "accessible" 1`] = `
|
||||
<span
|
||||
className=""
|
||||
|
||||
@@ -23,12 +23,21 @@ describe('modules/createDOMElement', () => {
|
||||
expect(component.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('prop "accessibilityRole"', () => {
|
||||
const accessibilityRole = 'banner';
|
||||
let component = renderer.create(createDOMElement('span', { accessibilityRole }));
|
||||
expect(component.toJSON()).toMatchSnapshot();
|
||||
component = renderer.create(createDOMElement('span', { accessibilityRole: 'button' }));
|
||||
expect(component.toJSON()).toMatchSnapshot();
|
||||
describe('prop "accessibilityRole"', () => {
|
||||
test('roles', () => {
|
||||
const component = renderer.create(createDOMElement('span', { accessibilityRole: 'banner' }));
|
||||
expect(component.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('button', () => {
|
||||
const component = renderer.create(createDOMElement('span', { accessibilityRole: 'button' }));
|
||||
expect(component.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('link and target="_blank"', () => {
|
||||
const component = renderer.create(createDOMElement('span', { accessibilityRole: 'link', target: '_blank' }));
|
||||
expect(component.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
test('prop "accessible"', () => {
|
||||
|
||||
@@ -42,6 +42,8 @@ const createDOMElement = (component, rnProps = {}) => {
|
||||
domProps.role = accessibilityRole;
|
||||
if (accessibilityRole === 'button') {
|
||||
domProps.type = 'button';
|
||||
} else if (accessibilityRole === 'link' && domProps.target === '_blank') {
|
||||
domProps.rel = `${domProps.rel || ''} noopener noreferrer`;
|
||||
}
|
||||
}
|
||||
if (type) { domProps.type = type; }
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
import ReactDOM from 'react-dom';
|
||||
const findNodeHandle = ReactDOM.findDOMNode;
|
||||
import findNodeHandle from 'react/lib/findDOMNode';
|
||||
export default findNodeHandle;
|
||||
|
||||
@@ -1,222 +0,0 @@
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* @generated SignedSource<<b68d78236d45828b3f7f7fcc740782a9>>
|
||||
*
|
||||
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
* !! This file is a check-in of a static_upstream project! !!
|
||||
* !! !!
|
||||
* !! You should not modify this file directly. Instead: !!
|
||||
* !! 1) Use `fjs use-upstream` to temporarily replace this with !!
|
||||
* !! the latest version from upstream. !!
|
||||
* !! 2) Make your changes, test them, etc. !!
|
||||
* !! 3) Use `fjs push-upstream` to copy your changes back to !!
|
||||
* !! static_upstream. !!
|
||||
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
*
|
||||
* Copyright 2013-2014 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @providesModule mergeHelpers
|
||||
*
|
||||
* requiresPolyfills: Array.isArray
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var invariant = require('fbjs/lib/invariant');
|
||||
var keyMirror = require('fbjs/lib/keyMirror');
|
||||
|
||||
/**
|
||||
* Maximum number of levels to traverse. Will catch circular structures.
|
||||
* @const
|
||||
*/
|
||||
var MAX_MERGE_DEPTH = 36;
|
||||
|
||||
/**
|
||||
* We won't worry about edge cases like new String('x') or new Boolean(true).
|
||||
* Functions are considered terminals, and arrays are not.
|
||||
* @param {*} o The item/object/value to test.
|
||||
* @return {boolean} true iff the argument is a terminal.
|
||||
*/
|
||||
var isTerminal = function(o) {
|
||||
return typeof o !== 'object' || o === null;
|
||||
};
|
||||
|
||||
var mergeHelpers = {
|
||||
|
||||
MAX_MERGE_DEPTH: MAX_MERGE_DEPTH,
|
||||
|
||||
isTerminal: isTerminal,
|
||||
|
||||
/**
|
||||
* Converts null/undefined values into empty object.
|
||||
*
|
||||
* @param {?Object=} arg Argument to be normalized (nullable optional)
|
||||
* @return {!Object}
|
||||
*/
|
||||
normalizeMergeArg: function(arg) {
|
||||
return arg === undefined || arg === null ? {} : arg;
|
||||
},
|
||||
|
||||
/**
|
||||
* If merging Arrays, a merge strategy *must* be supplied. If not, it is
|
||||
* likely the caller's fault. If this function is ever called with anything
|
||||
* but `one` and `two` being `Array`s, it is the fault of the merge utilities.
|
||||
*
|
||||
* @param {*} one Array to merge into.
|
||||
* @param {*} two Array to merge from.
|
||||
*/
|
||||
checkMergeArrayArgs: function(one, two) {
|
||||
invariant(
|
||||
Array.isArray(one) && Array.isArray(two),
|
||||
'Tried to merge arrays, instead got %s and %s.',
|
||||
one,
|
||||
two
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {*} one Object to merge into.
|
||||
* @param {*} two Object to merge from.
|
||||
*/
|
||||
checkMergeObjectArgs: function(one, two) {
|
||||
mergeHelpers.checkMergeObjectArg(one);
|
||||
mergeHelpers.checkMergeObjectArg(two);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {*} arg
|
||||
*/
|
||||
checkMergeObjectArg: function(arg) {
|
||||
invariant(
|
||||
!isTerminal(arg) && !Array.isArray(arg),
|
||||
'Tried to merge an object, instead got %s.',
|
||||
arg
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {*} arg
|
||||
*/
|
||||
checkMergeIntoObjectArg: function(arg) {
|
||||
invariant(
|
||||
(!isTerminal(arg) || typeof arg === 'function') && !Array.isArray(arg),
|
||||
'Tried to merge into an object, instead got %s.',
|
||||
arg
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks that a merge was not given a circular object or an object that had
|
||||
* too great of depth.
|
||||
*
|
||||
* @param {number} Level of recursion to validate against maximum.
|
||||
*/
|
||||
checkMergeLevel: function(level) {
|
||||
invariant(
|
||||
level < MAX_MERGE_DEPTH,
|
||||
'Maximum deep merge depth exceeded. You may be attempting to merge ' +
|
||||
'circular structures in an unsupported way.'
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks that the supplied merge strategy is valid.
|
||||
*
|
||||
* @param {string} Array merge strategy.
|
||||
*/
|
||||
checkArrayStrategy: function(strategy) {
|
||||
invariant(
|
||||
strategy === undefined || strategy in mergeHelpers.ArrayStrategies,
|
||||
'You must provide an array strategy to deep merge functions to ' +
|
||||
'instruct the deep merge how to resolve merging two arrays.'
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Set of possible behaviors of merge algorithms when encountering two Arrays
|
||||
* that must be merged together.
|
||||
* - `clobber`: The left `Array` is ignored.
|
||||
* - `indexByIndex`: The result is achieved by recursively deep merging at
|
||||
* each index. (not yet supported.)
|
||||
*/
|
||||
ArrayStrategies: keyMirror({
|
||||
Clobber: true,
|
||||
IndexByIndex: true
|
||||
})
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @generated SignedSource<<d3caa35be27b17ea4dd4c76bef72d1ab>>
|
||||
*
|
||||
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
* !! This file is a check-in of a static_upstream project! !!
|
||||
* !! !!
|
||||
* !! You should not modify this file directly. Instead: !!
|
||||
* !! 1) Use `fjs use-upstream` to temporarily replace this with !!
|
||||
* !! the latest version from upstream. !!
|
||||
* !! 2) Make your changes, test them, etc. !!
|
||||
* !! 3) Use `fjs push-upstream` to copy your changes back to !!
|
||||
* !! static_upstream. !!
|
||||
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
*
|
||||
* Copyright 2013-2014 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @providesModule mergeInto
|
||||
* @typechecks static-only
|
||||
*/
|
||||
|
||||
var checkMergeObjectArg = mergeHelpers.checkMergeObjectArg;
|
||||
var checkMergeIntoObjectArg = mergeHelpers.checkMergeIntoObjectArg;
|
||||
|
||||
/**
|
||||
* Shallow merges two structures by mutating the first parameter.
|
||||
*
|
||||
* @param {object|function} one Object to be merged into.
|
||||
* @param {?object} two Optional object with properties to merge from.
|
||||
*/
|
||||
function mergeInto(one, two) {
|
||||
checkMergeIntoObjectArg(one);
|
||||
if (two != null) {
|
||||
checkMergeObjectArg(two);
|
||||
for (var key in two) {
|
||||
if (!two.hasOwnProperty(key)) {
|
||||
continue;
|
||||
}
|
||||
one[key] = two[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var merge = function(one, two) {
|
||||
var result = {};
|
||||
mergeInto(result, one);
|
||||
mergeInto(result, two);
|
||||
return result;
|
||||
};
|
||||
|
||||
module.exports = merge;
|
||||
@@ -1,4 +1,4 @@
|
||||
const CSS_UNIT_RE = /^[+-]?\d*(?:\.\d+)?(?:[Ee][+-]?\d+)?(\w*)/;
|
||||
const CSS_UNIT_RE = /^[+-]?\d*(?:\.\d+)?(?:[Ee][+-]?\d+)?(%|\w*)/;
|
||||
|
||||
const getUnit = (str) => str.match(CSS_UNIT_RE)[1];
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { PropTypes } from 'react';
|
||||
const { array, bool, number, object, oneOf, oneOfType, string } = PropTypes;
|
||||
|
||||
const BaseComponentPropTypes = {
|
||||
const BaseComponentPropTypes = process.env.NODE_ENV !== 'production' ? {
|
||||
accessibilityLabel: string,
|
||||
accessibilityLiveRegion: oneOf([ 'assertive', 'off', 'polite' ]),
|
||||
accessibilityRole: string,
|
||||
accessible: bool,
|
||||
style: oneOfType([ array, number, object ]),
|
||||
testID: string
|
||||
};
|
||||
} : {};
|
||||
|
||||
module.exports = BaseComponentPropTypes;
|
||||
|
||||
@@ -4,7 +4,7 @@ import { PropTypes } from 'react';
|
||||
const numberOrString = PropTypes.oneOfType([ PropTypes.number, PropTypes.string ]);
|
||||
const BorderStylePropType = PropTypes.oneOf([ 'solid', 'dotted', 'dashed' ]);
|
||||
|
||||
const BorderPropTypes = {
|
||||
const BorderPropTypes = process.env.NODE_ENV !== 'production' ? {
|
||||
borderColor: ColorPropType,
|
||||
borderTopColor: ColorPropType,
|
||||
borderRightColor: ColorPropType,
|
||||
@@ -29,6 +29,6 @@ const BorderPropTypes = {
|
||||
borderBottomRightRadius$noI18n: numberOrString,
|
||||
borderLeftStyle$noI18n: BorderStylePropType,
|
||||
borderRightStyle$noI18n: BorderStylePropType
|
||||
};
|
||||
} : {};
|
||||
|
||||
module.exports = BorderPropTypes;
|
||||
|
||||
@@ -11,11 +11,10 @@
|
||||
*/
|
||||
|
||||
import { PropTypes } from 'react'
|
||||
import ReactPropTypeLocationNames from 'react/lib/ReactPropTypeLocationNames'
|
||||
|
||||
var normalizeColor = require('../modules/normalizeColor');
|
||||
|
||||
var colorPropType = function(isRequired, props, propName, componentName, location, propFullName) {
|
||||
var normalizeColor = require('../modules/normalizeColor');
|
||||
var ReactPropTypeLocationNames = require('react/lib/ReactPropTypeLocationNames');
|
||||
var color = props[propName];
|
||||
if (color === undefined || color === null) {
|
||||
if (isRequired) {
|
||||
@@ -56,7 +55,11 @@ var colorPropType = function(isRequired, props, propName, componentName, locatio
|
||||
}
|
||||
};
|
||||
|
||||
var ColorPropType = colorPropType.bind(null, false /* isRequired */);
|
||||
ColorPropType.isRequired = colorPropType.bind(null, true /* isRequired */);
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
var ColorPropType = colorPropType.bind(null, false /* isRequired */);
|
||||
ColorPropType.isRequired = colorPropType.bind(null, true /* isRequired */);
|
||||
} else {
|
||||
var ColorPropType = function () {}
|
||||
}
|
||||
|
||||
module.exports = ColorPropType
|
||||
|
||||
@@ -14,13 +14,11 @@
|
||||
|
||||
var PropTypes = require('react').PropTypes;
|
||||
|
||||
var createStrictShapeTypeChecker = require('./createStrictShapeTypeChecker');
|
||||
|
||||
var EdgeInsetsPropType = createStrictShapeTypeChecker({
|
||||
var EdgeInsetsPropType = process.env.NODE_ENV !== 'production' ? require('./createStrictShapeTypeChecker')({
|
||||
top: PropTypes.number,
|
||||
left: PropTypes.number,
|
||||
bottom: PropTypes.number,
|
||||
right: PropTypes.number,
|
||||
});
|
||||
}) : function () {};
|
||||
|
||||
module.exports = EdgeInsetsPropType;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { PropTypes } from 'react';
|
||||
const { number, oneOf, oneOfType, string } = PropTypes;
|
||||
const numberOrString = oneOfType([ number, string ]);
|
||||
|
||||
const LayoutPropTypes = {
|
||||
const LayoutPropTypes = process.env.NODE_ENV !== 'production' ? {
|
||||
// box model
|
||||
borderWidth: numberOrString,
|
||||
borderBottomWidth: numberOrString,
|
||||
@@ -58,6 +58,6 @@ const LayoutPropTypes = {
|
||||
paddingLeft$noI18n: numberOrString,
|
||||
paddingRight$noI18n: numberOrString,
|
||||
right$noI18n: numberOrString
|
||||
};
|
||||
} : {};
|
||||
|
||||
module.exports = LayoutPropTypes;
|
||||
|
||||
@@ -14,11 +14,9 @@
|
||||
|
||||
var PropTypes = require('react').PropTypes;
|
||||
|
||||
var createStrictShapeTypeChecker = require('./createStrictShapeTypeChecker');
|
||||
|
||||
var PointPropType = createStrictShapeTypeChecker({
|
||||
var PointPropType = process.env.NODE_ENV !== 'production' ? require('./createStrictShapeTypeChecker')({
|
||||
x: PropTypes.number,
|
||||
y: PropTypes.number,
|
||||
});
|
||||
}) : function () {};
|
||||
|
||||
module.exports = PointPropType;
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import createStrictShapeTypeChecker from './createStrictShapeTypeChecker';
|
||||
import flattenStyle from '../modules/flattenStyle';
|
||||
module.exports = process.env.NODE_ENV !== 'production' ? function StyleSheetPropType(shape) {
|
||||
const createStrictShapeTypeChecker = require('./createStrictShapeTypeChecker');
|
||||
const flattenStyle = require('../modules/flattenStyle');
|
||||
|
||||
module.exports = function StyleSheetPropType(shape) {
|
||||
const shapePropType = createStrictShapeTypeChecker(shape);
|
||||
return function (props, propName, componentName, location?) {
|
||||
let newProps = props;
|
||||
@@ -19,4 +19,4 @@ module.exports = function StyleSheetPropType(shape) {
|
||||
}
|
||||
return shapePropType(newProps, propName, componentName, location);
|
||||
};
|
||||
};
|
||||
} : function () {};
|
||||
|
||||
@@ -8,7 +8,7 @@ const ShadowOffsetPropType = shape({ width: number, height: number });
|
||||
const TextAlignPropType = oneOf([ 'center', 'inherit', 'justify', 'justify-all', 'left', 'right' ]);
|
||||
const WritingDirectionPropType = oneOf([ 'auto', 'ltr', 'rtl' ]);
|
||||
|
||||
const TextPropTypes = {
|
||||
const TextPropTypes = process.env.NODE_ENV !== 'production' ? {
|
||||
// box model
|
||||
color: ColorPropType,
|
||||
fontFamily: string,
|
||||
@@ -20,26 +20,23 @@ const TextPropTypes = {
|
||||
textAlign: TextAlignPropType,
|
||||
textAlignVertical: oneOf([ 'auto', 'bottom', 'center', 'top' ]),
|
||||
textDecorationLine: string,
|
||||
/* @platform web */
|
||||
textOverflow: string,
|
||||
/* @platform web */
|
||||
textRendering: oneOf([ 'auto', 'geometricPrecision', 'optimizeLegibility', 'optimizeSpeed' ]),
|
||||
textShadowColor: ColorPropType,
|
||||
textShadowOffset: ShadowOffsetPropType,
|
||||
textShadowRadius: number,
|
||||
/* @platform web */
|
||||
textTransform: oneOf([ 'capitalize', 'lowercase', 'none', 'uppercase' ]),
|
||||
/* @platform web */
|
||||
unicodeBidi: oneOf([ 'normal', 'bidi-override', 'embed', 'isolate', 'isolate-override', 'plaintext' ]),
|
||||
/* @platform web */
|
||||
whiteSpace: string,
|
||||
/* @platform web */
|
||||
wordWrap: string,
|
||||
writingDirection: WritingDirectionPropType,
|
||||
/* @platform web */
|
||||
textOverflow: string,
|
||||
textRendering: oneOf([ 'auto', 'geometricPrecision', 'optimizeLegibility', 'optimizeSpeed' ]),
|
||||
textTransform: oneOf([ 'capitalize', 'lowercase', 'none', 'uppercase' ]),
|
||||
unicodeBidi: oneOf([ 'normal', 'bidi-override', 'embed', 'isolate', 'isolate-override', 'plaintext' ]),
|
||||
whiteSpace: string,
|
||||
wordWrap: string,
|
||||
MozOsxFontSmoothing: string,
|
||||
WebkitFontSmoothing: string,
|
||||
// opt-out of RTL flipping
|
||||
textAlign$noI18n: TextAlignPropType,
|
||||
textShadowOffset$noI18n: ShadowOffsetPropType,
|
||||
writingDirection$noI18n: WritingDirectionPropType
|
||||
};
|
||||
} : {};
|
||||
|
||||
module.exports = TextPropTypes;
|
||||
|
||||
@@ -10,7 +10,7 @@ import { PropTypes } from 'react';
|
||||
const { arrayOf, number, oneOfType, shape, string } = PropTypes;
|
||||
const numberOrString = oneOfType([ number, string ]);
|
||||
|
||||
const TransformPropTypes = {
|
||||
const TransformPropTypes = process.env.NODE_ENV !== 'production' ? {
|
||||
transform: arrayOf(
|
||||
oneOfType([
|
||||
shape({ perspective: numberOrString }),
|
||||
@@ -29,6 +29,6 @@ const TransformPropTypes = {
|
||||
shape({ translate3d: string })
|
||||
])
|
||||
)
|
||||
};
|
||||
} : {};
|
||||
|
||||
module.exports = TransformPropTypes;
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
*/
|
||||
|
||||
import invariant from 'fbjs/lib/invariant'
|
||||
import merge from '../modules/merge'
|
||||
import ReactPropTypeLocationNames from 'react/lib/ReactPropTypeLocationNames'
|
||||
import ReactPropTypesSecret from 'react/lib/ReactPropTypesSecret'
|
||||
|
||||
@@ -43,7 +42,7 @@ function createStrictShapeTypeChecker(
|
||||
}
|
||||
// We need to check all keys in case some are required but missing from
|
||||
// props.
|
||||
var allKeys = merge(props[propName], shapeTypes);
|
||||
var allKeys = { ...props[propName], ...shapeTypes };
|
||||
for (var key in allKeys) {
|
||||
var checker = shapeTypes[key];
|
||||
if (!checker) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const path = require('path')
|
||||
const webpack = require('webpack')
|
||||
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
|
||||
|
||||
const DIST_DIRECTORY = './dist'
|
||||
|
||||
@@ -7,6 +8,16 @@ module.exports = {
|
||||
entry: {
|
||||
main: DIST_DIRECTORY
|
||||
},
|
||||
externals: [
|
||||
{
|
||||
react: {
|
||||
root: 'React',
|
||||
commonjs2: 'react',
|
||||
commonjs: 'react',
|
||||
amd: 'react'
|
||||
}
|
||||
}
|
||||
],
|
||||
output: {
|
||||
filename: 'ReactNative.js',
|
||||
library: 'ReactNative',
|
||||
@@ -14,12 +25,16 @@ module.exports = {
|
||||
path: DIST_DIRECTORY
|
||||
},
|
||||
plugins: [
|
||||
new BundleAnalyzerPlugin({
|
||||
analyzerMode: 'static',
|
||||
openAnalyzer: false
|
||||
}),
|
||||
new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('production') }),
|
||||
new webpack.optimize.DedupePlugin(),
|
||||
// https://github.com/animatedjs/animated/issues/40
|
||||
new webpack.NormalModuleReplacementPlugin(
|
||||
/es6-set/,
|
||||
path.join(__dirname, 'src/modules/polyfills/Set.js')
|
||||
path.join(__dirname, 'dist/modules/polyfills/Set.js')
|
||||
),
|
||||
new webpack.optimize.OccurenceOrderPlugin(),
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
|
||||
Reference in New Issue
Block a user