mirror of
https://github.com/zhigang1992/react-native-web.git
synced 2026-03-30 17:34:05 +08:00
Compare commits
29 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 |
@@ -5,5 +5,4 @@ before_script:
|
||||
- export DISPLAY=:99.0
|
||||
- sh -e /etc/init.d/xvfb start
|
||||
script:
|
||||
- npm run lint
|
||||
- npm test
|
||||
|
||||
@@ -57,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
|
||||
|
||||
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}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -62,16 +62,13 @@ AppRegistry.runApplication('App', {
|
||||
initialProps: {},
|
||||
rootTag: document.getElementById('react-app')
|
||||
})
|
||||
|
||||
// prerender the app
|
||||
const { html, styleElement } = AppRegistry.prerenderApplication('App', { initialProps })
|
||||
```
|
||||
|
||||
## Server-side rendering
|
||||
|
||||
Rendering using the `AppRegistry`:
|
||||
|
||||
```
|
||||
```js
|
||||
import ReactDOMServer from 'react-dom/server'
|
||||
import ReactNative, { AppRegistry } from 'react-native'
|
||||
|
||||
@@ -84,4 +81,4 @@ AppRegistry.registerComponent('App', () => AppContainer)
|
||||
// prerender the app
|
||||
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,13 +1,15 @@
|
||||
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); } }
|
||||
onScroll={onScroll}
|
||||
scrollEventThrottle={1000} // 1 event per second
|
||||
style={styles.scrollViewStyle}
|
||||
>
|
||||
@@ -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'}
|
||||
|
||||
11
package.json
11
package.json
@@ -1,10 +1,12 @@
|
||||
{
|
||||
"name": "react-native-web",
|
||||
"version": "0.0.52",
|
||||
"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,8 +16,9 @@
|
||||
"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",
|
||||
|
||||
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;
|
||||
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;
|
||||
@@ -35,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());
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import ReactNativePropRegistry from '../../modules/ReactNativePropRegistry';
|
||||
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 };
|
||||
|
||||
|
||||
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;
|
||||
@@ -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 });
|
||||
}
|
||||
|
||||
@@ -2,18 +2,27 @@ import applyNativeMethods from '../../modules/applyNativeMethods';
|
||||
import ListViewDataSource from './ListViewDataSource';
|
||||
import ListViewPropTypes from './ListViewPropTypes';
|
||||
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 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: []
|
||||
};
|
||||
|
||||
@@ -26,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() {
|
||||
@@ -40,58 +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;
|
||||
}
|
||||
}
|
||||
|
||||
return React.cloneElement(this.props.renderScrollComponent(this.props), {
|
||||
ref: this._setScrollViewRef
|
||||
const {
|
||||
renderScrollComponent,
|
||||
...props
|
||||
} = this.props;
|
||||
Object.assign(props, {
|
||||
onScroll: this._onScroll,
|
||||
stickyHeaderIndices: this.props.stickyHeaderIndices.concat(sectionHeaderIndices),
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,35 @@ 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
|
||||
*/
|
||||
@@ -75,13 +104,13 @@ 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) {
|
||||
|
||||
@@ -21,7 +21,6 @@ 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' ]),
|
||||
@@ -237,7 +236,6 @@ const styles = StyleSheet.create({
|
||||
overflowY: 'hidden'
|
||||
},
|
||||
contentContainer: {
|
||||
flexGrow: 1,
|
||||
transform: [ { translateZ: 0 } ]
|
||||
},
|
||||
contentContainerHorizontal: {
|
||||
|
||||
@@ -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,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',
|
||||
|
||||
@@ -270,7 +270,6 @@ const styles = StyleSheet.create({
|
||||
borderWidth: 0,
|
||||
boxSizing: 'border-box',
|
||||
color: 'inherit',
|
||||
flex: 1,
|
||||
font: 'inherit',
|
||||
padding: 0
|
||||
}
|
||||
|
||||
@@ -18,10 +18,12 @@ 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');
|
||||
@@ -77,7 +79,7 @@ var TouchableHighlight = React.createClass({
|
||||
* active.
|
||||
*/
|
||||
underlayColor: ColorPropType,
|
||||
style: View.propTypes.style,
|
||||
style: StyleSheetPropType(ViewStylePropTypes),
|
||||
/**
|
||||
* Called immediately after the underlay is shown
|
||||
*/
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import findNodeHandle from './modules/findNodeHandle';
|
||||
import ReactDefaultInjection from 'react/lib/ReactDefaultInjection';
|
||||
import { render, unmountComponentAtNode } from 'react/lib/ReactMount';
|
||||
|
||||
ReactDefaultInjection.inject();
|
||||
|
||||
// APIs
|
||||
import I18nManager from './apis/I18nManager';
|
||||
import StyleSheet from './apis/StyleSheet';
|
||||
@@ -11,7 +14,11 @@ 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,
|
||||
unmountComponentAtNode,
|
||||
|
||||
12
src/index.js
12
src/index.js
@@ -1,15 +1,20 @@
|
||||
import findNodeHandle from './modules/findNodeHandle';
|
||||
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';
|
||||
@@ -20,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';
|
||||
@@ -34,10 +40,10 @@ import TouchableWithoutFeedback from './components/Touchable/TouchableWithoutFee
|
||||
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';
|
||||
@@ -53,10 +59,12 @@ const ReactNative = {
|
||||
AppRegistry,
|
||||
AppState,
|
||||
AsyncStorage,
|
||||
Clipboard,
|
||||
Dimensions,
|
||||
Easing,
|
||||
I18nManager,
|
||||
InteractionManager,
|
||||
Linking,
|
||||
NetInfo,
|
||||
PanResponder,
|
||||
PixelRatio,
|
||||
@@ -67,6 +75,7 @@ const ReactNative = {
|
||||
|
||||
// components
|
||||
ActivityIndicator,
|
||||
Button,
|
||||
Image,
|
||||
ListView,
|
||||
ProgressBar,
|
||||
@@ -81,6 +90,7 @@ const ReactNative = {
|
||||
View,
|
||||
|
||||
// modules
|
||||
createDOMElement,
|
||||
NativeModules,
|
||||
|
||||
// propTypes
|
||||
|
||||
@@ -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,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];
|
||||
|
||||
|
||||
@@ -20,22 +20,19 @@ const TextPropTypes = process.env.NODE_ENV !== 'production' ? {
|
||||
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,
|
||||
|
||||
@@ -34,7 +34,7 @@ module.exports = {
|
||||
// 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