Compare commits

..

10 Commits

Author SHA1 Message Date
Nicolas Gallagher
21eeafabd5 0.0.38 2016-07-12 21:19:28 -07:00
Nicolas Gallagher
249f157ed9 [fix] ListView child layout
Fix #166
2016-07-12 21:18:18 -07:00
Nicolas Gallagher
0f8cff6124 0.0.37 2016-07-12 17:47:40 -07:00
Nicolas Gallagher
30bf00a3bc [fix] TextInput styles
Ref #166
2016-07-12 17:47:10 -07:00
Nicolas Gallagher
f4515a3995 0.0.36 2016-07-12 13:56:34 -07:00
Nicolas Gallagher
17b30aceb2 [fix] default DOM element for 'View' (part 2)
First patch: 41159bcb10

@chriskjaer mentioned that changing from 'div' to 'span' introduces
different validation errors, e.g., <span><form>a</form></span>.

This patch uses 'context' to switch to a 'span' element if a 'View' is
being rendered within a 'button' element.
2016-07-12 11:03:31 -07:00
Nicolas Gallagher
5f3f4db7a6 [fix] iOS Touchable click handling 2016-07-12 10:26:00 -07:00
Nicolas Gallagher
eb8aa0a9db [add] 'selectable' prop to Text 2016-07-12 10:23:40 -07:00
Nicolas Gallagher
af60504ca4 [add] Vibration API 2016-07-11 21:51:00 -07:00
Nicolas Gallagher
41159bcb10 [fix] default DOM element for 'View'
There are certain contexts where using a `div` is invalid HTML and may
cause rendering issues. Change the default element created by
`createReactDOMComponent` to a `span`.

**Appendix**

The following HTML results a validator error.

  <!DOCTYPE html>
  <head>
  <meta charset="utf-8">
  <title>test</title>
  <button><div>a</div></button>

Error: Element `div` not allowed as child of element `button` in this
context.

Source: https://validator.w3.org/nu/#textarea
2016-07-11 20:25:05 -07:00
19 changed files with 156 additions and 32 deletions

View File

@@ -2,7 +2,7 @@
[![Build Status][travis-image]][travis-url]
[![npm version][npm-image]][npm-url]
![gzipped size](https://img.shields.io/badge/gzipped-~41.0k-blue.svg)
![gzipped size](https://img.shields.io/badge/gzipped-~48.6k-blue.svg)
[React Native][react-native-url] components and APIs for the Web.
@@ -121,6 +121,7 @@ Exported modules:
* [`PixelRatio`](docs/apis/PixelRatio.md)
* [`Platform`](docs/apis/Platform.md)
* [`StyleSheet`](docs/apis/StyleSheet.md)
* [`Vibration`](docs/apis/Vibration.md)
## License

View File

@@ -20,7 +20,7 @@ const styles = StyleSheet.create({
## Methods
**select**: any
**select**(object): any
`Platform.select` takes an object containing `Platform.OS` as keys and returns
the value for the platform you are currently running on.

35
docs/apis/Vibration.md Normal file
View File

@@ -0,0 +1,35 @@
# Vibration
Vibration is described as a pattern of on-off pulses, which may be of varying
lengths. The pattern may consist of either a single integer, describing the
number of milliseconds to vibrate, or an array of integers describing a pattern
of vibrations and pauses. Vibration is controlled with a single method:
`Vibration.vibrate()`.
The vibration is asynchronous so this method will return immediately. There
will be no effect on devices that do not support vibration.
## Methods
static **cancel**()
Stop the vibration.
static **vibrate**(pattern)
Start the vibration pattern.
## Examples
Vibrate once for 200ms:
```js
Vibration.vibrate(200);
Vibration.vibrate([200]);
```
Vibrate for 200ms, pause for 100ms, vibrate for 200ms:
```js
Vibration.vibrate([200, 100, 200]);
```

View File

@@ -32,7 +32,7 @@ Note: Avoid changing `accessibilityRole` values over time or after user
actions. Generally, accessibility APIs do not provide a means of notifying
assistive technologies of a `role` value change.
(web) **accessible**: bool = true
**accessible**: bool = true
When `false`, the text is hidden from assistive technologies. (This is
implemented using `aria-hidden`.)
@@ -54,6 +54,10 @@ height } } }`, where `x` and `y` are the offsets from the parent node.
This function is called on press.
**selectable**: bool = true
Lets the user select the text.
**style**: style
+ ...[View#style](View.md)

View File

@@ -83,6 +83,7 @@ export default class App extends React.Component {
<Heading size='large'>TextInput</Heading>
<TextInput
defaultValue='Default textInput'
keyboardType='default'
onBlur={(e) => { console.log('TextInput.onBlur', e) }}
onChange={(e) => { console.log('TextInput.onChange', e) }}
@@ -90,20 +91,21 @@ export default class App extends React.Component {
onFocus={(e) => { console.log('TextInput.onFocus', e) }}
onSelectionChange={(e) => { console.log('TextInput.onSelectionChange', e) }}
/>
<TextInput secureTextEntry />
<TextInput defaultValue='read only' editable={false} />
<TextInput secureTextEntry style={styles.textInput} />
<TextInput defaultValue='read only' editable={false} style={styles.textInput} />
<TextInput
style={{ flex:1, height: 60, padding: 20, fontSize: 20, textAlign: 'center' }}
style={[ styles.textInput, { flex:1, height: 60, padding: 20, fontSize: 20, textAlign: 'center' } ]}
keyboardType='email-address' placeholder='you@domain.com' placeholderTextColor='red'
/>
<TextInput keyboardType='numeric' />
<TextInput keyboardType='phone-pad' />
<TextInput defaultValue='https://delete-me' keyboardType='url' placeholder='https://www.some-website.com' selectTextOnFocus />
<TextInput keyboardType='numeric' style={styles.textInput} />
<TextInput keyboardType='phone-pad' style={styles.textInput} />
<TextInput defaultValue='https://delete-me' keyboardType='url' placeholder='https://www.some-website.com' selectTextOnFocus style={styles.textInput} />
<TextInput
defaultValue='default value'
maxNumberOfLines={10}
multiline
numberOfLines={5}
style={styles.textInput}
/>
<Heading size='large'>Touchable</Heading>
@@ -229,6 +231,9 @@ const styles = StyleSheet.create({
flexDirection: 'row',
flexWrap: 'wrap'
},
textInput: {
borderWidth: 1
},
box: {
alignItems: 'center',
flexGrow: 1,

View File

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

View File

@@ -0,0 +1,20 @@
const vibrate = (pattern) => {
if ('vibrate' in window.navigator) {
if (typeof pattern === 'number' || Array.isArray(pattern)) {
window.navigator.vibrate(pattern)
} else {
throw new Error('Vibration pattern should be a number or array')
}
}
}
const Vibration = {
cancel() {
vibrate(0)
},
vibrate(pattern) {
vibrate(pattern)
}
}
module.exports = Vibration

View File

@@ -3,6 +3,8 @@ import React, { Component } from 'react'
import ScrollView from '../ScrollView'
import ListViewDataSource from './ListViewDataSource'
import ListViewPropTypes from './ListViewPropTypes'
import View from '../View'
import pick from 'lodash/pick'
const SCROLLVIEW_REF = 'listviewscroll'
@@ -64,7 +66,7 @@ class ListView extends Component {
if (renderSectionHeader) {
const section = dataSource.getSectionHeaderData(sectionIdx)
const key = 's_' + sectionId
const child = <div key={key}>{renderSectionHeader(section, sectionId)}</div>
const child = <View key={key}>{renderSectionHeader(section, sectionId)}</View>
children.push(child)
}
@@ -73,7 +75,7 @@ class ListView extends Component {
const rowId = rows[rowIdx]
const row = dataSource.getRowData(sectionIdx, rowIdx)
const key = 'r_' + sectionId + '_' + rowId
const child = <div key={key}>{renderRow(row, sectionId, rowId, this.onRowHighlighted)}</div>
const child = <View key={key}>{renderRow(row, sectionId, rowId, this.onRowHighlighted)}</View>
children.push(child)
// render optional separator
@@ -88,12 +90,9 @@ class ListView extends Component {
}
}
const {
renderScrollComponent,
...props
} = this.props
const props = pick(ScrollView.propTypes, this.props)
return React.cloneElement(renderScrollComponent(props), {
return React.cloneElement(this.props.renderScrollComponent(props), {
ref: SCROLLVIEW_REF
}, header, children, footer)
}

View File

@@ -31,4 +31,11 @@ suite('components/Text', () => {
done()
}
})
test('prop "selectable"', () => {
let text = shallow(<Text />)
assert.equal(text.prop('style').userSelect, undefined)
text = shallow(<Text selectable={false} />)
assert.equal(text.prop('style').userSelect, 'none')
})
})

View File

@@ -11,18 +11,20 @@ class Text extends Component {
static propTypes = {
accessibilityLabel: createReactDOMComponent.propTypes.accessibilityLabel,
accessibilityRole: createReactDOMComponent.propTypes.accessibilityRole,
accessibilityRole: PropTypes.oneOf([ 'heading', 'link' ]),
accessible: createReactDOMComponent.propTypes.accessible,
children: PropTypes.any,
numberOfLines: PropTypes.number,
onLayout: PropTypes.func,
onPress: PropTypes.func,
selectable: PropTypes.bool,
style: StyleSheetPropType(TextStylePropTypes),
testID: createReactDOMComponent.propTypes.testID
};
static defaultProps = {
accessible: true
accessible: true,
selectable: true
};
render() {
@@ -30,6 +32,7 @@ class Text extends Component {
numberOfLines,
onLayout, // eslint-disable-line
onPress, // eslint-disable-line
selectable,
style,
...other
} = this.props
@@ -41,6 +44,7 @@ class Text extends Component {
style: [
styles.initial,
style,
!selectable && styles.notSelectable,
numberOfLines === 1 && styles.singleLineStyle
]
})
@@ -63,6 +67,9 @@ const styles = StyleSheet.create({
textDecorationLine: 'none',
wordWrap: 'break-word'
},
notSelectable: {
userSelect: 'none'
},
singleLineStyle: {
maxWidth: '100%',
overflow: 'hidden',

View File

@@ -223,6 +223,19 @@ suite('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)
assert.equal(StyleSheet.flatten(textInput.prop('style')).borderWidth, 1, 'expected View styles to be applied to the "View"')
assert.equal(input.prop('style').textAlign, 'center', 'expected Text styles to be applied to the "input"')
})
test('prop "value"', () => {
const value = 'value'
const input = findNativeInput(shallow(<TextInput value={value} />))

View File

@@ -121,8 +121,9 @@ class TextInput extends Component {
// In order to support 'Text' styles on 'TextInput', we split the 'Text'
// and 'View' styles and apply them to the 'Text' and 'View' components
// used in the implementation
const rootStyles = pick(style, viewStyleProps)
const textStyles = omit(style, viewStyleProps)
const flattenedStyle = StyleSheet.flatten(style)
const rootStyles = pick(flattenedStyle, viewStyleProps)
const textStyles = omit(flattenedStyle, viewStyleProps)
const propsCommon = {
autoComplete: autoComplete && 'on',
@@ -236,8 +237,7 @@ applyNativeMethods(TextInput)
const styles = StyleSheet.create({
initial: {
borderColor: 'black',
borderWidth: 1
borderColor: 'black'
},
wrapper: {
flex: 1

View File

@@ -247,7 +247,7 @@ var TouchableHighlight = React.createClass({
onResponderRelease={this.touchableHandleResponderRelease}
onResponderTerminate={this.touchableHandleResponderTerminate}
ref={UNDERLAY_REF}
style={this.state.underlayStyle}
style={[ styles.root, this.state.underlayStyle ]}
tabIndex='0'
testID={this.props.testID}>
{React.cloneElement(

View File

@@ -19,6 +19,7 @@ var Touchable = require('./Touchable');
var View = require('../View');
var ensurePositiveDelayProps = require('./ensurePositiveDelayProps');
var warning = require('fbjs/lib/warning');
var StyleSheet = require('../../apis/StyleSheet');
type Event = Object;
@@ -160,8 +161,8 @@ 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') ?
[child.props.style, {color: 'red'}] :
child.props.style;
[styles.root, child.props.style, {color: 'red'}] :
[styles.root, child.props.style];
return (React: any).cloneElement(child, {
accessible: this.props.accessible !== false,
accessibilityLabel: this.props.accessibilityLabel,
@@ -182,4 +183,10 @@ const TouchableWithoutFeedback = React.createClass({
}
});
var styles = StyleSheet.create({
root: {
cursor: 'pointer'
}
});
module.exports = TouchableWithoutFeedback;

View File

@@ -7,8 +7,20 @@ import View from '../'
import { mount, shallow } from 'enzyme'
suite('components/View', () => {
suite('rendered element', () => {
test('is a "div" by default', () => {
const view = shallow(<View />)
assert.equal(view.is('div'), true)
})
test('is a "span" when inside <View accessibilityRole="button" />', () => {
const view = mount(<View accessibilityRole='button'><View /></View>)
assert.equal(view.find('span').length, 1)
})
})
test('prop "children"', () => {
const children = 'children'
const children = <View testID='1' />
const view = shallow(<View>{children}</View>)
assert.equal(view.prop('children'), children)
})

View File

@@ -50,9 +50,18 @@ class View extends Component {
style: {}
};
constructor(props, context) {
super(props, context)
this._normalizeEventForHandler = this._normalizeEventForHandler.bind(this)
static childContextTypes = {
isInAButtonView: PropTypes.bool
};
static contextTypes = {
isInAButtonView: PropTypes.bool
};
getChildContext() {
return {
isInAButtonView: this.props.accessibilityRole === 'button'
}
}
render() {
@@ -72,6 +81,7 @@ class View extends Component {
const props = {
...other,
component: this.context.isInAButtonView ? 'span' : 'div',
onClick: this._normalizeEventForHandler(this.props.onClick),
onClickCapture: this._normalizeEventForHandler(this.props.onClickCapture),
onTouchCancel: this._normalizeEventForHandler(this.props.onTouchCancel),

View File

@@ -18,6 +18,7 @@ import PixelRatio from './apis/PixelRatio'
import Platform from './apis/Platform'
import StyleSheet from './apis/StyleSheet'
import UIManager from './apis/UIManager'
import Vibration from './apis/Vibration'
// components
import ActivityIndicator from './components/ActivityIndicator'
@@ -65,6 +66,7 @@ const ReactNative = {
Platform,
StyleSheet,
UIManager,
Vibration,
// components
ActivityIndicator,

View File

@@ -43,7 +43,9 @@ suite('modules/createReactDOMComponent', () => {
test('prop "component"', () => {
const component = 'main'
const element = shallow(createReactDOMComponent({ component }))
let element = shallow(createReactDOMComponent({}))
assert.equal(element.is('span'), true, 'Default element must be a "span"')
element = shallow(createReactDOMComponent({ component }))
assert.equal(element.is('main'), true)
})

View File

@@ -22,7 +22,7 @@ const createReactDOMComponent = ({
accessibilityLiveRegion,
accessibilityRole,
accessible = true,
component = 'div',
component = 'span',
testID,
type,
...other