Compare commits

...

7 Commits

Author SHA1 Message Date
Nicolas Gallagher
acc7032d60 0.0.43 2016-08-16 14:05:48 -07:00
Nicolas Gallagher
0fc5644959 Add more details to README
Fix #57
2016-08-16 14:04:22 -07:00
Nicolas Gallagher
be923ec453 [fix] disabled Touchable 2016-08-16 10:29:26 -07:00
Nicolas Gallagher
248c2e1258 [fix] ActivityIndicator animation
Use 'Animated' to animate the 'ActivityIndicator'

Fix #182
2016-08-15 16:58:20 -07:00
Nicolas Gallagher
2e822c068d [fix] Image render thrashing
This patch removes several avoidable uses of `setState`, only
re-rendering the `Image` when necessary.

It also fixes a style prop warning for `resizeMode`, which was not being
removed in development due to the use of `Object.freeze` when styles are
registered.

Close #116
2016-08-15 15:00:47 -07:00
Nicolas Gallagher
fb5406e4ec [change] I18nManager manages global writing direction 2016-08-15 11:36:59 -07:00
Vitor Balocco
638479991e Fix broken link and typo in StyleSheet API docs (#189) 2016-08-14 19:42:42 -07:00
12 changed files with 192 additions and 102 deletions

View File

@@ -7,23 +7,16 @@
Browser support: Chrome, Firefox, Safari >= 7, IE 10, Edge.
[npm-image]: https://badge.fury.io/js/react-native-web.svg
[npm-url]: https://npmjs.org/package/react-native-web
[react-native-url]: https://facebook.github.io/react-native/
[travis-image]: https://travis-ci.org/necolas/react-native-web.svg?branch=master
[travis-url]: https://travis-ci.org/necolas/react-native-web
## Overview
"React Native for Web" is a project to bring React Native's building blocks and
touch handling to the Web.
React Native unlike React DOM is a comprehensive UI framework for
application developers. React Native's components are higher-level building
blocks than those provided by React DOM. React Native also provides
platform-agnostic JavaScript APIs for animating and styling components,
responding to touch events, and interacting with the host environment.
Bringing the React Native APIs and components to the Web allows React Native
components to be run on the Web platform. But it also solves several problems
facing the React DOM ecosystem: no framework-level animation or styling
solution; difficulty sharing and composing UI components (without pulling in
their build or runtime dependencies); and the lack of high-level base
components.
touch handling to the Web. [Read more](#why).
## Quick start
@@ -40,11 +33,8 @@ using [react-native-web-starter](https://github.com/grabcode/react-native-web-st
## Examples
Demos:
* React Native [examples running on Web](https://necolas.github.io/react-native-web/storybook/)
* [React Native for Web: Playground](http://codepen.io/necolas/pen/PZzwBR).
* [TicTacToe](http://codepen.io/necolas/full/eJaLZd/)
* [2048](http://codepen.io/necolas/full/wMVvxj/)
Sample:
@@ -124,12 +114,40 @@ Exported modules:
* [`StyleSheet`](docs/apis/StyleSheet.md)
* [`Vibration`](docs/apis/Vibration.md)
<span id="#why"></span>
## Why?
React Native is a comprehensive JavaScript framework for building application
user interfaces. It provides high-level, platform-agnostic components and APIs
e.g., `Text`, `View`, `Touchable*`, `Animated`, `StyleSheet` - that simplify
working with layout, gestures, animations, and styles. The entire React Native
ecosystem can depend on these shared building blocks.
In contrast, the React DOM ecosystem is limited by the lack of a higher-level
framework. At Twitter, we want to seamlessly author and share React component
libraries between different Web applications (with increasing interest from
product teams for multi-platform solutions). This goal draws together multiple,
inter-related problems including: styling, animation, gestures, themes,
viewport adaptation, accessibility, diverse build processes, and RTL layouts.
Almost all these problems are avoided, solved, or can be solved in React
Native. Central to this is React Native's JavaScript style API (not strictly
"CSS-in-JS") which avoids the key [problems with
CSS](https://speakerdeck.com/vjeux/react-css-in-js). By giving up some of the
complexity of CSS it also provides a reliable surface for style composition,
animation, gestures, server-side rendering, RTL layout; and removes the
requirement for CSS-specific build tools.
Bringing the React Native APIs and components to the Web has the added benefit
of allowing teams to explore code-sharing between Native and Web platforms.
## Related projects
* [react-native-web-starter](https://github.com/grabcode/react-native-web-starter)
* [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)
## License
React Native for Web is [BSD licensed](LICENSE).
[npm-image]: https://badge.fury.io/js/react-native-web.svg
[npm-url]: https://npmjs.org/package/react-native-web
[react-native-url]: https://facebook.github.io/react-native/
[travis-image]: https://travis-ci.org/necolas/react-native-web.svg?branch=master
[travis-url]: https://travis-ci.org/necolas/react-native-web

View File

@@ -1,8 +1,6 @@
# I18nManager
Control and set the layout and writing direction of the application. You must
set `dir="rtl"` (and should set `lang="${lang}"`) on the root element of your
app.
Control and set the layout and writing direction of the application.
## Properties
@@ -16,12 +14,12 @@ static **allowRTL**(allowRTL: bool)
Allow the application to display in RTL mode.
static **forceRTL**(allowRTL: bool)
static **forceRTL**(forceRTL: bool)
Force the application to display in RTL mode.
static **setRTL**(allowRTL: bool)
static **setPreferredLanguageRTL**(isRTL: bool)
Set the application to display in RTL mode. You will need to determine the
user's preferred locale and if it is an RTL language. (This is best done on the
server as it is notoriously inaccurate to deduce client-side.)
Set the application's preferred writing direction to RTL. You will need to
determine the user's preferred locale server-side (from HTTP headers) and
decide whether it's an RTL language.

View File

@@ -2,8 +2,8 @@
The `StyleSheet` abstraction converts predefined styles to (vendor-prefixed)
CSS without requiring a compile-time step. Some styles cannot be resolved
outside of the render loop and are applied as inline styles. Read more about to
[how style your application](docs/guides/style).
outside of the render loop and are applied as inline styles. Read more about
[how to style your application](../guides/style.md).
## Methods

View File

@@ -406,6 +406,7 @@ const examples = [
);
},
},
/*
{
title: 'Tint Color',
description: 'The `tintColor` style prop changes all the non-alpha ' +
@@ -456,6 +457,7 @@ const examples = [
);
},
},
*/
{
title: 'Resize Mode',
description: 'The `resizeMode` style prop controls how the image is ' +

View File

@@ -1,6 +1,6 @@
{
"name": "react-native-web",
"version": "0.0.42",
"version": "0.0.43",
"description": "React Native for Web",
"main": "dist/index.js",
"files": [

View File

@@ -6,32 +6,40 @@ import I18nManager from '..'
suite('apis/I18nManager', () => {
suite('when RTL not enabled', () => {
setup(() => {
I18nManager.setRTL(false)
I18nManager.setPreferredLanguageRTL(false)
})
test('is "false" by default', () => {
assert.equal(I18nManager.isRTL, false)
assert.equal(document.documentElement.getAttribute('dir'), 'ltr')
})
test('is "true" when forced', () => {
I18nManager.forceRTL(true)
assert.equal(I18nManager.isRTL, true)
assert.equal(document.documentElement.getAttribute('dir'), 'rtl')
I18nManager.forceRTL(false)
})
})
suite('when RTL is enabled', () => {
setup(() => {
I18nManager.setRTL(true)
I18nManager.setPreferredLanguageRTL(true)
})
teardown(() => {
I18nManager.setPreferredLanguageRTL(false)
})
test('is "true" by default', () => {
assert.equal(I18nManager.isRTL, true)
assert.equal(document.documentElement.getAttribute('dir'), 'rtl')
})
test('is "false" when not allowed', () => {
I18nManager.allowRTL(false)
assert.equal(I18nManager.isRTL, false)
assert.equal(document.documentElement.getAttribute('dir'), 'ltr')
I18nManager.allowRTL(true)
})
})

View File

@@ -1,3 +1,5 @@
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment'
type I18nManagerStatus = {
allowRTL: (allowRTL: boolean) => {},
forceRTL: (forceRTL: boolean) => {},
@@ -5,28 +7,38 @@ type I18nManagerStatus = {
isRTL: boolean
}
let isApplicationLanguageRTL = false
let isPreferredLanguageRTL = false
let isRTLAllowed = true
let isRTLForced = false
const isRTL = () => {
if (isRTLForced) {
return true
}
return isRTLAllowed && isPreferredLanguageRTL
}
const onChange = () => {
if (ExecutionEnvironment.canUseDOM) {
document.documentElement.setAttribute('dir', isRTL() ? 'rtl' : 'ltr')
}
}
const I18nManager: I18nManagerStatus = {
allowRTL(bool) {
isRTLAllowed = bool
onChange()
},
forceRTL(bool) {
isRTLForced = bool
onChange()
},
setRTL(bool) {
isApplicationLanguageRTL = bool
setPreferredLanguageRTL(bool) {
isPreferredLanguageRTL = bool
onChange()
},
get isRTL() {
if (isRTLForced) {
return true
}
if (isRTLAllowed && isApplicationLanguageRTL) {
return true
}
return false
return isRTL()
}
}

View File

@@ -1,30 +1,20 @@
import Animated from '../../apis/Animated'
import applyNativeMethods from '../../modules/applyNativeMethods'
import Easing from 'animated/lib/Easing'
import React, { Component, PropTypes } from 'react'
import ReactDOM from 'react-dom'
import StyleSheet from '../../apis/StyleSheet'
import View from '../View'
const GRAY = '#999999'
const animationEffectTimingProperties = {
direction: 'alternate',
duration: 700,
easing: 'ease-in-out',
fill: 'forwards',
iterations: Infinity
}
const keyframeEffects = [
{ transform: 'scale(1)', opacity: 1.0 },
{ transform: 'scale(0.95)', opacity: 0.5 }
]
const opacityInterpolation = { inputRange: [ 0, 1 ], outputRange: [ 0.5, 1 ] }
const scaleInterpolation = { inputRange: [ 0, 1 ], outputRange: [ 0.95, 1 ] }
class ActivityIndicator extends Component {
static propTypes = {
animating: PropTypes.bool,
color: PropTypes.string,
hidesWhenStopped: PropTypes.bool,
size: PropTypes.oneOf(['small', 'large']),
size: PropTypes.oneOf([ 'small', 'large' ]),
style: View.propTypes.style
};
@@ -36,10 +26,14 @@ class ActivityIndicator extends Component {
style: {}
};
componentDidMount() {
if (document.documentElement.animate) {
this._player = ReactDOM.findDOMNode(this._indicatorRef).animate(keyframeEffects, animationEffectTimingProperties)
constructor(props) {
super(props)
this.state = {
animation: new Animated.Value(1)
}
}
componentDidMount() {
this._manageAnimation()
}
@@ -57,37 +51,55 @@ class ActivityIndicator extends Component {
...other
} = this.props
const { animation } = this.state
return (
<View {...other} style={[ styles.container, style ]}>
<View
ref={this._createIndicatorRef}
<Animated.View
style={[
indicatorStyles[size],
hidesWhenStopped && !animating && styles.hidesWhenStopped,
{ borderColor: color }
{
borderColor: color,
opacity: animation.interpolate(opacityInterpolation),
transform: [ { scale: animation.interpolate(scaleInterpolation) } ]
}
]}
/>
</View>
)
}
_createIndicatorRef = (component) => {
this._indicatorRef = component
}
_manageAnimation() {
if (this._player) {
if (this.props.animating) {
this._player.play()
} else {
this._player.cancel()
}
const { animation } = this.state
const cycleAnimation = () => {
Animated.sequence([
Animated.timing(animation, {
duration: 600,
easing: Easing.inOut(Easing.ease),
toValue: 0
}),
Animated.timing(animation, {
duration: 600,
easing: Easing.inOut(Easing.ease),
toValue: 1
})
]).start((event) => {
if (event.finished) {
cycleAnimation()
}
})
}
if (this.props.animating) {
cycleAnimation()
} else {
animation.stopAnimation()
}
}
}
applyNativeMethods(ActivityIndicator)
const styles = StyleSheet.create({
container: {
alignItems: 'center',
@@ -113,4 +125,4 @@ const indicatorStyles = StyleSheet.create({
}
})
module.exports = ActivityIndicator
module.exports = applyNativeMethods(ActivityIndicator)

View File

@@ -51,17 +51,18 @@ class Image extends Component {
constructor(props, context) {
super(props, context)
const uri = resolveAssetSource(props.source)
this.state = { status: uri ? STATUS_PENDING : STATUS_IDLE }
this._imageState = uri ? STATUS_PENDING : STATUS_IDLE
this.state = { isLoaded: false }
}
componentDidMount() {
if (this.state.status === STATUS_PENDING) {
if (this._imageState === STATUS_PENDING) {
this._createImageLoader()
}
}
componentDidUpdate() {
if (this.state.status === STATUS_PENDING && !this.image) {
if (this._imageState === STATUS_PENDING && !this.image) {
this._createImageLoader()
}
}
@@ -69,9 +70,7 @@ class Image extends Component {
componentWillReceiveProps(nextProps) {
const nextUri = resolveAssetSource(nextProps.source)
if (resolveAssetSource(this.props.source) !== nextUri) {
this.setState({
status: nextUri ? STATUS_PENDING : STATUS_IDLE
})
this._updateImageState(nextUri ? STATUS_PENDING : STATUS_IDLE)
}
}
@@ -80,6 +79,7 @@ class Image extends Component {
}
render() {
const { isLoaded } = this.state
const {
accessibilityLabel,
accessible,
@@ -90,13 +90,13 @@ class Image extends Component {
testID
} = this.props
const isLoaded = this.state.status === STATUS_LOADED
const displayImage = resolveAssetSource(!isLoaded ? defaultSource : source)
const backgroundImage = displayImage ? `url("${displayImage}")` : null
const style = StyleSheet.flatten(this.props.style)
let style = StyleSheet.flatten(this.props.style)
const resizeMode = this.props.resizeMode || style.resizeMode || ImageResizeMode.cover
// remove resizeMode style, as it is not supported by View
// remove 'resizeMode' style, as it is not supported by View (N.B. styles are frozen in dev)
style = process.env.NODE_ENV !== 'production' ? { ...style } : style
delete style.resizeMode
/**
@@ -153,7 +153,7 @@ class Image extends Component {
const event = { nativeEvent: e }
this._destroyImageLoader()
this.setState({ status: STATUS_ERRORED })
this._updateImageState(STATUS_ERRORED)
this._onLoadEnd()
if (onError) onError(event)
}
@@ -163,7 +163,7 @@ class Image extends Component {
const event = { nativeEvent: e }
this._destroyImageLoader()
this.setState({ status: STATUS_LOADED })
this._updateImageState(STATUS_LOADED)
if (onLoad) onLoad(event)
this._onLoadEnd()
}
@@ -175,9 +175,17 @@ class Image extends Component {
_onLoadStart() {
const { onLoadStart } = this.props
this.setState({ status: STATUS_LOADING })
this._updateImageState(STATUS_LOADING)
if (onLoadStart) onLoadStart()
}
_updateImageState(status) {
this._imageState = status
const isLoaded = this._imageState === STATUS_LOADED
if (isLoaded !== this.state.isLoaded) {
this.setState({ isLoaded })
}
}
}
applyNativeMethods(Image)

View File

@@ -31,6 +31,7 @@ var merge = require('../../modules/merge');
type Event = Object;
var DEFAULT_PROPS = {
accessibilityRole: 'button',
activeOpacity: 0.8,
underlayColor: 'black'
};
@@ -234,7 +235,8 @@ var TouchableHighlight = React.createClass({
<View
accessible={true}
accessibilityLabel={this.props.accessibilityLabel}
accessibilityRole={this.props.accessibilityRole || this.props.accessibilityTraits || 'button'}
accessibilityRole={this.props.accessibilityRole}
disabled={this.props.disabled}
hitSlop={this.props.hitSlop}
onKeyDown={(e) => { this._onKeyEnter(e, this.touchableHandleActivePressIn) }}
onKeyPress={(e) => { this._onKeyEnter(e, this.touchableHandlePress) }}
@@ -247,8 +249,12 @@ var TouchableHighlight = React.createClass({
onResponderRelease={this.touchableHandleResponderRelease}
onResponderTerminate={this.touchableHandleResponderTerminate}
ref={UNDERLAY_REF}
style={[ styles.root, this.state.underlayStyle ]}
tabIndex='0'
style={[
styles.root,
this.props.disabled && styles.disabled,
this.state.underlayStyle
]}
tabIndex={this.props.disabled ? null : '0'}
testID={this.props.testID}>
{React.cloneElement(
React.Children.only(this.props.children),
@@ -274,6 +280,9 @@ var styles = StyleSheet.create({
root: {
cursor: 'pointer',
userSelect: 'none'
},
disabled: {
cursor: 'default'
}
});

View File

@@ -64,6 +64,7 @@ var TouchableOpacity = React.createClass({
getDefaultProps: function() {
return {
accessibilityRole: 'button',
activeOpacity: 0.2,
};
},
@@ -168,8 +169,14 @@ var TouchableOpacity = React.createClass({
<Animated.View
accessible={this.props.accessible !== false}
accessibilityLabel={this.props.accessibilityLabel}
accessibilityRole={this.props.accessibilityRole || 'button'}
style={[styles.root, this.props.style, {opacity: this.state.anim}]}
accessibilityRole={this.props.accessibilityRole}
disabled={this.props.disabled}
style={[
styles.root,
this.props.disabled && styles.disabled,
this.props.style,
{opacity: this.state.anim}
]}
testID={this.props.testID}
onLayout={this.props.onLayout}
hitSlop={this.props.hitSlop}
@@ -182,7 +189,7 @@ var TouchableOpacity = React.createClass({
onResponderMove={this.touchableHandleResponderMove}
onResponderRelease={this.touchableHandleResponderRelease}
onResponderTerminate={this.touchableHandleResponderTerminate}
tabIndex='0'
tabIndex={this.props.disabled ? null : '0'}
>
{this.props.children}
</Animated.View>
@@ -194,6 +201,9 @@ var styles = StyleSheet.create({
root: {
cursor: 'pointer',
userSelect: 'none'
},
disabled: {
cursor: 'default'
}
});

View File

@@ -161,12 +161,22 @@ const TouchableWithoutFeedback = React.createClass({
children.push(Touchable.renderDebugView({color: 'red', hitSlop: this.props.hitSlop}));
}
const style = (Touchable.TOUCH_TARGET_DEBUG && child.type && child.type.displayName === 'Text') ?
[styles.root, child.props.style, {color: 'red'}] :
[styles.root, child.props.style];
[
styles.root,
this.props.disabled && styles.disabled,
child.props.style,
{color: 'red'}
] :
[
styles.root,
this.props.disabled && styles.disabled,
child.props.style
];
return (React: any).cloneElement(child, {
accessible: this.props.accessible !== false,
accessibilityLabel: this.props.accessibilityLabel,
accessibilityRole: this.props.accessibilityRole,
disabled: this.props.disabled,
testID: this.props.testID,
onLayout: this.props.onLayout,
hitSlop: this.props.hitSlop,
@@ -178,7 +188,7 @@ const TouchableWithoutFeedback = React.createClass({
onResponderTerminate: this.touchableHandleResponderTerminate,
style,
children,
tabIndex: '0'
tabIndex: this.props.disabled ? null : '0'
});
}
});
@@ -186,6 +196,9 @@ const TouchableWithoutFeedback = React.createClass({
var styles = StyleSheet.create({
root: {
cursor: 'pointer'
},
disabled: {
cursor: 'default'
}
});