Compare commits

...

14 Commits

Author SHA1 Message Date
Nicolas Gallagher
fd04d65b03 0.0.12 2015-12-20 03:24:57 -08:00
Nicolas Gallagher
0ab984f507 [change] TextInput: implement placeholder and placeholderTextColor
Without access to the Shadow DOM pseudo-elements, the placeholder
behaviour needs to be reimplemented.

Update to match React Native's modification to `TextInput` to include
all `View` props and use the `Text` style props.

Fix #12
Fix #48
2015-12-20 03:11:39 -08:00
Nicolas Gallagher
3d1ad50a58 Update documentation and examples 2015-12-19 10:59:22 -08:00
Nicolas Gallagher
92554321df [add] Text: support all ViewStylePropTypes 2015-12-19 10:51:29 -08:00
Nicolas Gallagher
1c9270c4ea [fix] ReactNative export pattern 2015-12-19 10:49:49 -08:00
Nicolas Gallagher
8a5f9cd7d9 0.0.11 2015-12-19 06:04:03 -08:00
Nicolas Gallagher
aac6b796b2 Replace 'EnvironmentPlugin' with 'DefinePlugin' 2015-12-19 05:47:20 -08:00
Nicolas Gallagher
c77ce19f1b [fix] StyleSheet: escaping of class names in CSS
Fix #47
2015-12-19 04:22:00 -08:00
Nicolas Gallagher
25b74d30c4 [fix] Text: style props 2015-12-19 03:57:04 -08:00
Nicolas Gallagher
4191d58694 Fix styles in 'GridView' example 2015-12-19 03:27:44 -08:00
Nicolas Gallagher
11b861ae64 [add] support for 'list' and 'listitem' accessibilityRole mapping
Fix #49
2015-12-19 03:25:40 -08:00
Nicolas Gallagher
68bf08112a [fix] StylePropTypes: add support for 'verticalAlign'
Fix #45
2015-12-19 03:11:55 -08:00
Nicolas Gallagher
b277b3e509 [fix] StylePropTypes: add support for 'boxShadow'
Fix #44
2015-12-19 03:11:12 -08:00
Nicolas Gallagher
c135dddbd1 [fix] StyleSheet: isStyleObject check 2015-12-14 15:28:40 -08:00
29 changed files with 328 additions and 271 deletions

View File

@@ -128,7 +128,8 @@ reactStyleSheet.textContent = StyleSheet.renderToString()
### [`StyleSheet`](docs/apis/StyleSheet.md)
StyleSheet is a style abstraction that transforms inline styles to CSS on the
client or the server. It provides a minimal CSS reset.
client or the server. It provides a minimal CSS reset targeting elements and
pseudo-elements beyond the reach of React inline styles.
## Components
@@ -147,7 +148,7 @@ A scrollable view with event throttling.
### [`Text`](docs/components/Text.md)
Displays text as an inline block and supports basic press handling.
Displays text inline and supports basic press handling.
### [`TextInput`](docs/components/TextInput.md)

View File

@@ -1,17 +1,21 @@
var webpack = require('webpack')
var DedupePlugin = webpack.optimize.DedupePlugin
var EnvironmentPlugin = webpack.EnvironmentPlugin
var DefinePlugin = webpack.DefinePlugin
var OccurenceOrderPlugin = webpack.optimize.OccurenceOrderPlugin
var UglifyJsPlugin = webpack.optimize.UglifyJsPlugin
var plugins = [
new DedupePlugin(),
new EnvironmentPlugin('NODE_ENV'),
new OccurenceOrderPlugin()
]
if (process.env.NODE_ENV === 'production') {
plugins.push(
new DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
})
)
plugins.push(
new UglifyJsPlugin({
compress: {

View File

@@ -57,7 +57,7 @@ could be an http address or a base64 encoded image.
**style**: style
[View](View.md) style
+ ...[View#style](View.md)
Defaults:
@@ -76,11 +76,9 @@ Used to locate a view in end-to-end tests.
```js
import placeholderAvatar from './placeholderAvatar.png'
import React, { Image, StyleSheet } from 'react-native-web'
import React, { Component, Image, PropTypes, StyleSheet } from 'react-native-web'
const { Component, PropTypes } = React;
export default class Avatar extends Component {
export default class ImageExample extends Component {
constructor(props, context) {
super(props, context)
this.state = { loading: true }
@@ -112,7 +110,11 @@ export default class Avatar extends Component {
onLoad={this._onLoad.bind(this)}
resizeMode='cover'
source={{ uri: user.avatarUrl }}
style={{ ...styles.base, ...styles[size], ...loadingStyle }}
style={{
...styles.base,
...styles[size],
...loadingStyle
}}
/>
)
}
@@ -121,23 +123,23 @@ export default class Avatar extends Component {
const styles = StyleSheet.create({
base: {
borderColor: 'white',
borderRadius: '5px',
borderWidth: '5px'
borderRadius: 5,
borderWidth: 5
},
loading: {
opacity: 0.5
},
small: {
height: '32px',
width: '32px'
height: 32,
width: 32
},
normal: {
height: '48px',
width: '48px'
height: 48,
width: 48
},
large: {
height: '64px',
width: '64px'
height: 64,
width: 64
}
})
```

View File

@@ -10,28 +10,17 @@ Content to display over the image.
**style**: style
+ `property` type
Defaults:
```js
{
}
```
+ ...[View#style](View.md)
## Examples
```js
import React, { ListView } from 'react-native-web'
import React, { Component, ListView, PropTypes } from 'react-native-web'
const { Component, PropTypes } = React;
export default class ListViewExample extends Component {
static propTypes = {}
class Example extends Component {
static propTypes = {
}
static defaultProps = {
}
static defaultProps = {}
render() {
return (

View File

@@ -38,16 +38,15 @@ time the view is scrolled.
**style**: style
[View](View.md) style
+ ...[View#style](View.md)
## Examples
```js
import React, { ScrollView, StyleSheet } from 'react-native-web'
import React, { Component, ScrollView, StyleSheet } from 'react-native-web'
import Item from './Item'
export default class App extends React.Component {
export default class ScrollViewExample extends Component {
constructor(props, context) {
super(props, context)
this.state = {
@@ -75,10 +74,10 @@ export default class App extends React.Component {
const styles = StyleSheet.create({
root: {
borderWidth: '1px'
borderWidth: 1
},
container: {
padding: '10px'
padding: 10
}
})
```

View File

@@ -1,13 +1,11 @@
# Text
`Text` is component for displaying text. It supports style, basic touch
handling, and inherits typographic styles from ancestor elements. In a
divergence from React Native, components other than `Text` can be children of a
`Text` component.
handling, and inherits typographic styles from ancestor elements.
The `Text` is unique relative to layout: child elements use text layout
(`inline-block`) rather than flexbox layout. This means that elements inside of
a `Text` are not rectangles, as they wrap when reaching the edge of their
(`inline`) rather than flexbox layout. This means that elements inside of a
`Text` are not rectangles, as they wrap when reaching the edge of their
container.
Unsupported React Native props:
@@ -53,22 +51,20 @@ This function is called on press.
**style**: style
+ `backgroundColor`
+ ...[View#style](View.md)
+ `color`
+ `direction`
+ `fontFamily`
+ `fontSize`
+ `fontStyle`
+ `fontWeight`
+ `letterSpacing`
+ `lineHeight`
+ `margin`
+ `padding`
+ `textAlign`
+ `textDecoration`
+ `textTransform`
+ `whiteSpace`
+ `wordWrap`
+ `writingDirection`
**testID**: string
@@ -77,18 +73,18 @@ Used to locate this view in end-to-end tests.
## Examples
```js
import React, { StyleSheet, Text } from 'react-native-web'
import React, { Component, PropTypes, StyleSheet, Text } from 'react-native-web'
const { Component, PropTypes } = React
class PrettyText extends Component {
export default class PrettyText extends Component {
static propTypes = {
...Text.propTypes,
color: PropTypes.oneOf(['white', 'gray', 'red']),
size: PropTypes.oneOf(['small', 'normal', 'large']),
weight: PropTypes.oneOf(['light', 'normal', 'bold'])
}
static defaultProps = {
...Text.defaultProps,
color: 'gray',
size: 'normal',
weight: 'normal'
@@ -102,16 +98,16 @@ class PrettyText extends Component {
...other
style={{
...style,
...localStyle.color[color],
...localStyle.size[size],
...localStyle.weight[weight]
...styles.color[color],
...styles.size[size],
...styles.weight[weight]
}}
/>
);
}
}
const localStyle = StyleSheet.create({
const styles = StyleSheet.create({
color: {
white: { color: 'white' },
gray: { color: 'gray' },

View File

@@ -48,9 +48,10 @@ updating the `value` prop to keep the controlled state in sync.
If `false`, text is not editable (i.e., read-only).
**keyboardType**: oneOf('default', 'email-address', 'numeric', 'phone-pad', 'url') = 'default'
**keyboardType**: oneOf('default', 'email-address', 'numeric', 'phone-pad', 'search', 'url', 'web-search') = 'default'
Determines which keyboard to open.
Determines which keyboard to open. (NOTE: Safari iOS requires an ancestral
`<form action>` element to display the `search` keyboard).
(Not available when `multiline` is `true`.)
@@ -111,11 +112,12 @@ object is passed as an argument to the callback handler.
**placeholder**: string
The string that will be rendered before text input has been entered.
The string that will be rendered in an empty `TextInput` before text has been
entered.
**placeholderTextColor**: string
TODO. The text color of the placeholder string.
The text color of the placeholder string.
**secureTextEntry**: bool = false
@@ -130,19 +132,8 @@ If `true`, all text will automatically be selected on focus.
**style**: style
[View](View.md) style
+ `color`
+ `direction`
+ `fontFamily`
+ `fontSize`
+ `fontStyle`
+ `fontWeight`
+ `letterSpacing`
+ `lineHeight`
+ `textAlign`
+ `textDecoration`
+ `textTransform`
+ ...[Text#style](Text.md)
+ `outline`
**testID**: string
@@ -159,14 +150,18 @@ user edits to the value set `editable={false}`.
## Examples
```js
import React, { StyleSheet, TextInput } from 'react-native-web'
import React, { Component, StyleSheet, TextInput } from 'react-native-web'
export default class AppTextInput extends React.Component {
export default class TextInputExample extends Component {
constructor(props, context) {
super(props, context)
this.state = { isFocused: false }
}
_onBlur(e) {
this.setState({ isFocused: false })
}
_onFocus(e) {
this.setState({ isFocused: true })
}
@@ -178,6 +173,7 @@ export default class AppTextInput extends React.Component {
maxNumberOfLines={5}
multiline
numberOfLines={2}
onBlur={this._onBlur.bind(this)}
onFocus={this._onFocus.bind(this)}
placeholder={`What's happening?`}
style={{
@@ -192,7 +188,7 @@ export default class AppTextInput extends React.Component {
const styles = StyleSheet.create({
default: {
borderColor: 'gray',
borderWidth: '0 0 2px 0'
borderBottomWidth: 2
},
focused: {
borderColor: 'blue'

View File

@@ -79,21 +79,17 @@ Delay in ms, from the release of the touch, before `onPressOut` is called.
**style**: style
[View](View.md) style
+ ...[View#style](View.md)
## Examples
```js
import React, { Touchable } from 'react-native-web'
import React, { Component, PropTypes, Touchable } from 'react-native-web'
const { Component, PropTypes } = React;
export default class Example extends Component {
static propTypes = {}
class Example extends Component {
static propTypes = {
}
static defaultProps = {
}
static defaultProps = {}
render() {
return (

View File

@@ -109,6 +109,12 @@ from `style`.
+ `justifyContent`
+ `left`
+ `margin`
+ `marginBottom`
+ `marginHorizontal`
+ `marginLeft`
+ `marginRight`
+ `marginTop`
+ `marginVertical`
+ `maxHeight`
+ `maxWidth`
+ `minHeight`
@@ -119,6 +125,12 @@ from `style`.
+ `overflowX`
+ `overflowY`
+ `padding`
+ `paddingBottom`
+ `paddingHorizontal`
+ `paddingLeft`
+ `paddingRight`
+ `paddingTop`
+ `paddingVertical`
+ `position`
+ `right`
+ `top`
@@ -155,11 +167,9 @@ Used to locate this view in end-to-end tests.
## Examples
```js
import React, { StyleSheet, View } from 'react-native-web'
import React, { Component, PropTypes, StyleSheet, View } from 'react-native-web'
const { Component, PropTypes } = React
class Example extends Component {
export default class ViewExample extends Component {
render() {
return (
<View style={styles.row}>
@@ -181,6 +191,4 @@ const styles = StyleSheet.create({
flexGrow: 1
}
})
export default Example
```

View File

@@ -95,10 +95,10 @@ export default class App extends React.Component {
/>
<TextInput secureTextEntry />
<TextInput defaultValue='read only' editable={false} />
<TextInput keyboardType='email-address' />
<TextInput keyboardType='email-address' placeholder='you@domain.com' placeholderTextColor='red' />
<TextInput keyboardType='numeric' />
<TextInput keyboardType='phone-pad' />
<TextInput keyboardType='url' selectTextOnFocus />
<TextInput defaultValue='https://delete-me' keyboardType='url' placeholder='https://www.some-website.com' selectTextOnFocus />
<TextInput
defaultValue='default value'
maxNumberOfLines={10}

View File

@@ -1,6 +1,4 @@
import React, { StyleSheet, View } from '../../src'
const { Component, PropTypes } = React
import React, { Component, PropTypes, StyleSheet, View } from '../../src'
const styles = StyleSheet.create({
root: {
@@ -42,8 +40,8 @@ export default class GridView extends Component {
const contentContainerStyle = {
...styles.contentContainer,
margin: `0 calc(-0.5 * ${alley})`,
padding: `0 ${gutter}`
marginHorizontal: `calc(-0.5 * ${alley})`,
paddingHorizontal: `${gutter}`
}
const newChildren = React.Children.map(children, (child) => {
@@ -51,7 +49,7 @@ export default class GridView extends Component {
style: {
...child.props.style,
...styles.column,
margin: `0 calc(0.5 * ${alley})`
marginHorizontal: `calc(0.5 * ${alley})`
}
})
})

View File

@@ -1,7 +1,9 @@
import React from 'react'
import { StyleSheet, Text } from '../../src'
import React, { StyleSheet, Text } from '../../src'
const headingStyles = StyleSheet.create({
const styles = StyleSheet.create({
root: {
fontFamily: '"Helvetica Neue", arial, sans-serif'
},
size: {
xlarge: {
fontSize: '2rem',
@@ -24,7 +26,7 @@ const Heading = ({ children, size = 'normal' }) => (
<Text
accessibilityRole='heading'
children={children}
style={headingStyles.size[size]}
style={{ ...styles.root, ...styles.size[size] }}
/>
)

View File

@@ -28,7 +28,7 @@ const MediaQueryWidget = ({ mediaQuery = {} }) => {
return (
<View style={styles.root}>
<Text style={styles.heading}>Active Media Query</Text>
<Text>{`"${active.alias}"`} {active.mql.media}</Text>
<Text>{`"${active.alias}"`} {active.mql && active.mql.media}</Text>
</View>
)
}

View File

@@ -3,7 +3,6 @@
<title>React Native for Web</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="The core React Native components adapted and expanded upon for the web">
<style>html { font-family: sans-serif; }</style>
<style id="react-stylesheet"></style>
<div id="react-root"></div>
<script src="/examples.js"></script>

View File

@@ -1,6 +1,6 @@
{
"name": "react-native-web",
"version": "0.0.10",
"version": "0.0.12",
"description": "React Native for Web",
"main": "dist/react-native-web.js",
"files": [
@@ -10,7 +10,7 @@
"build": "rm -rf ./dist && webpack --config config/webpack.config.publish.js --sort-assets-by --progress",
"examples": "webpack-dev-server --config config/webpack.config.example.js --inline --hot --colors --quiet",
"lint": "eslint config examples src",
"prepublish": "NODE_ENV=publish npm run build",
"prepublish": "npm run build",
"test": "npm run lint && npm run test:unit",
"test:unit": "karma start config/karma.config.js",
"test:watch": "npm run test:unit -- --no-single-run"

View File

@@ -11,6 +11,8 @@ const roleComponents = {
form: 'form',
heading: 'h1',
link: 'a',
list: 'ul',
listitem: 'li',
main: 'main',
navigation: 'nav',
region: 'section'

View File

@@ -1,35 +1,22 @@
import { pickProps } from '../../modules/filterObjectProps'
import CoreComponent from '../CoreComponent'
import View from '../View'
export default {
...View.stylePropTypes,
...pickProps(CoreComponent.stylePropTypes, [
'backgroundColor',
'color',
'direction',
'font',
'fontFamily',
'fontSize',
'fontStyle',
'fontWeight',
'letterSpacing',
'lineHeight',
'margin',
'marginHorizontal',
'marginVertical',
'marginBottom',
'marginLeft',
'marginRight',
'marginTop',
'padding',
'paddingHorizontal',
'paddingVertical',
'paddingBottom',
'paddingLeft',
'paddingRight',
'paddingTop',
'textAlign',
'textDecoration',
'textTransform',
'whiteSpace',
'wordWrap'
'wordWrap',
'writingDirection'
])
}

View File

@@ -1,20 +1,7 @@
import { pickProps } from '../../modules/filterObjectProps'
import View from '../View'
import CoreComponent from '../CoreComponent'
import React from 'react'
import Text from '../Text'
export default {
...(View.stylePropTypes),
...pickProps(CoreComponent.stylePropTypes, [
'color',
'direction',
'fontFamily',
'fontSize',
'fontStyle',
'fontWeight',
'letterSpacing',
'lineHeight',
'textAlign',
'textDecoration',
'textTransform'
])
...Text.stylePropTypes,
outline: React.PropTypes.string
}

View File

@@ -7,6 +7,10 @@ import ReactTestUtils from 'react-addons-test-utils'
import TextInput from '../'
const findInput = (dom) => dom.querySelector('input, textarea')
const findShallowInput = (vdom) => vdom.props.children.props.children[0]
const findShallowPlaceholder = (vdom) => vdom.props.children.props.children[1]
suite('components/TextInput', () => {
test('prop "accessibilityLabel"', () => {
const accessibilityLabel = 'accessibilityLabel'
@@ -16,123 +20,120 @@ suite('components/TextInput', () => {
test('prop "autoComplete"', () => {
// off
let dom = utils.renderToDOM(<TextInput />)
assert.equal(dom.getAttribute('autocomplete'), undefined)
let input = findInput(utils.renderToDOM(<TextInput />))
assert.equal(input.getAttribute('autocomplete'), undefined)
// on
dom = utils.renderToDOM(<TextInput autoComplete />)
assert.equal(dom.getAttribute('autocomplete'), 'on')
input = findInput(utils.renderToDOM(<TextInput autoComplete />))
assert.equal(input.getAttribute('autocomplete'), 'on')
})
test('prop "autoFocus"', () => {
// false
let dom = utils.renderToDOM(<TextInput />)
let input = findInput(utils.renderToDOM(<TextInput />))
assert.deepEqual(document.activeElement, document.body)
// true
dom = utils.renderToDOM(<TextInput autoFocus />)
assert.deepEqual(document.activeElement, dom)
input = findInput(utils.renderToDOM(<TextInput autoFocus />))
assert.deepEqual(document.activeElement, input)
})
utils.testIfDocumentFocused('prop "clearTextOnFocus"', () => {
const defaultValue = 'defaultValue'
// false
let dom = utils.renderAndInject(<TextInput defaultValue={defaultValue} />)
dom.focus()
assert.equal(dom.value, defaultValue)
let input = findInput(utils.renderAndInject(<TextInput defaultValue={defaultValue} />))
input.focus()
assert.equal(input.value, defaultValue)
// true
dom = utils.renderAndInject(<TextInput clearTextOnFocus defaultValue={defaultValue} />)
dom.focus()
assert.equal(dom.value, '')
input = findInput(utils.renderAndInject(<TextInput clearTextOnFocus defaultValue={defaultValue} />))
input.focus()
assert.equal(input.value, '')
})
test('prop "defaultValue"', () => {
const defaultValue = 'defaultValue'
const result = utils.shallowRender(<TextInput defaultValue={defaultValue} />)
assert.equal(result.props.defaultValue, defaultValue)
const input = findShallowInput(utils.shallowRender(<TextInput defaultValue={defaultValue} />))
assert.equal(input.props.defaultValue, defaultValue)
})
test('prop "editable"', () => {
// true
let dom = utils.renderToDOM(<TextInput />)
assert.equal(dom.getAttribute('readonly'), undefined)
let input = findInput(utils.renderToDOM(<TextInput />))
assert.equal(input.getAttribute('readonly'), undefined)
// false
dom = utils.renderToDOM(<TextInput editable={false} />)
assert.equal(dom.getAttribute('readonly'), '')
input = findInput(utils.renderToDOM(<TextInput editable={false} />))
assert.equal(input.getAttribute('readonly'), '')
})
test('prop "keyboardType"', () => {
// default
let dom = utils.renderToDOM(<TextInput />)
assert.equal(dom.getAttribute('type'), undefined)
dom = utils.renderToDOM(<TextInput keyboardType='default' />)
assert.equal(dom.getAttribute('type'), undefined)
let input = findInput(utils.renderToDOM(<TextInput />))
assert.equal(input.getAttribute('type'), undefined)
input = findInput(utils.renderToDOM(<TextInput keyboardType='default' />))
assert.equal(input.getAttribute('type'), undefined)
// email-address
dom = utils.renderToDOM(<TextInput keyboardType='email-address' />)
assert.equal(dom.getAttribute('type'), 'email')
input = findInput(utils.renderToDOM(<TextInput keyboardType='email-address' />))
assert.equal(input.getAttribute('type'), 'email')
// numeric
dom = utils.renderToDOM(<TextInput keyboardType='numeric' />)
assert.equal(dom.getAttribute('type'), 'number')
input = findInput(utils.renderToDOM(<TextInput keyboardType='numeric' />))
assert.equal(input.getAttribute('type'), 'number')
// phone-pad
dom = utils.renderToDOM(<TextInput keyboardType='phone-pad' />)
assert.equal(dom.getAttribute('type'), 'tel')
input = findInput(utils.renderToDOM(<TextInput keyboardType='phone-pad' />))
assert.equal(input.getAttribute('type'), 'tel')
// url
dom = utils.renderToDOM(<TextInput keyboardType='url' />)
assert.equal(dom.getAttribute('type'), 'url')
input = findInput(utils.renderToDOM(<TextInput keyboardType='url' />))
assert.equal(input.getAttribute('type'), 'url')
})
test('prop "maxLength"', () => {
let dom = utils.renderToDOM(<TextInput />)
assert.equal(dom.getAttribute('maxlength'), undefined)
dom = utils.renderToDOM(<TextInput maxLength={10} />)
assert.equal(dom.getAttribute('maxlength'), '10')
let input = findInput(utils.renderToDOM(<TextInput />))
assert.equal(input.getAttribute('maxlength'), undefined)
input = findInput(utils.renderToDOM(<TextInput maxLength={10} />))
assert.equal(input.getAttribute('maxlength'), '10')
})
test('prop "maxNumberOfLines"', () => {
const style = { borderWidth: 0, fontSize: 20, lineHeight: 1 }
const generateValue = () => {
let str = ''
while (str.length < 100) str += 'x'
return str
}
let dom = utils.renderAndInject(
let input = findInput(utils.renderAndInject(
<TextInput
maxNumberOfLines={3}
multiline
style={style}
value={generateValue()}
/>
)
const height = dom.getBoundingClientRect().height
))
const height = input.getBoundingClientRect().height
// need a range because of cross-browser differences
assert.ok(height >= 60, height)
assert.ok(height <= 66, height)
assert.ok(height >= 42, height)
assert.ok(height <= 48, height)
})
test('prop "multiline"', () => {
// false
let dom = utils.renderToDOM(<TextInput />)
assert.equal(dom.tagName, 'INPUT')
let input = findInput(utils.renderToDOM(<TextInput />))
assert.equal(input.tagName, 'INPUT')
// true
dom = utils.renderToDOM(<TextInput multiline />)
assert.equal(dom.tagName, 'TEXTAREA')
input = findInput(utils.renderToDOM(<TextInput multiline />))
assert.equal(input.tagName, 'TEXTAREA')
})
test('prop "numberOfLines"', () => {
const style = { borderWidth: 0, fontSize: 20, lineHeight: 1 }
// missing multiline
let dom = utils.renderToDOM(<TextInput numberOfLines={2} />)
assert.equal(dom.tagName, 'INPUT')
let input = findInput(utils.renderToDOM(<TextInput numberOfLines={2} />))
assert.equal(input.tagName, 'INPUT')
// with multiline
dom = utils.renderAndInject(<TextInput multiline numberOfLines={2} style={style} />)
assert.equal(dom.tagName, 'TEXTAREA')
const height = dom.getBoundingClientRect().height
input = findInput(utils.renderAndInject(<TextInput multiline numberOfLines={2} />))
assert.equal(input.tagName, 'TEXTAREA')
const height = input.getBoundingClientRect().height
// need a range because of cross-browser differences
assert.ok(height >= 40)
assert.ok(height <= 46)
assert.ok(height >= 30, height)
assert.ok(height <= 36, height)
})
test('prop "onBlur"', (done) => {
const input = utils.renderToDOM(<TextInput onBlur={onBlur} />)
const input = findInput(utils.renderToDOM(<TextInput onBlur={onBlur} />))
ReactTestUtils.Simulate.blur(input)
function onBlur(e) {
assert.ok(e)
@@ -141,7 +142,7 @@ suite('components/TextInput', () => {
})
test('prop "onChange"', (done) => {
const input = utils.renderToDOM(<TextInput onChange={onChange} />)
const input = findInput(utils.renderToDOM(<TextInput onChange={onChange} />))
ReactTestUtils.Simulate.change(input)
function onChange(e) {
assert.ok(e)
@@ -151,7 +152,7 @@ suite('components/TextInput', () => {
test('prop "onChangeText"', (done) => {
const newText = 'newText'
const input = utils.renderToDOM(<TextInput onChangeText={onChangeText} />)
const input = findInput(utils.renderToDOM(<TextInput onChangeText={onChangeText} />))
ReactTestUtils.Simulate.change(input, { target: { value: newText } })
function onChangeText(text) {
assert.equal(text, newText)
@@ -160,7 +161,7 @@ suite('components/TextInput', () => {
})
test('prop "onFocus"', (done) => {
const input = utils.renderToDOM(<TextInput onFocus={onFocus} />)
const input = findInput(utils.renderToDOM(<TextInput onFocus={onFocus} />))
ReactTestUtils.Simulate.focus(input)
function onFocus(e) {
assert.ok(e)
@@ -171,7 +172,7 @@ suite('components/TextInput', () => {
test('prop "onLayout"')
test('prop "onSelectionChange"', (done) => {
const input = utils.renderAndInject(<TextInput defaultValue='12345' onSelectionChange={onSelectionChange} />)
const input = findInput(utils.renderAndInject(<TextInput defaultValue='12345' onSelectionChange={onSelectionChange} />))
ReactTestUtils.Simulate.select(input, { target: { selectionStart: 0, selectionEnd: 3 } })
function onSelectionChange(e) {
assert.equal(e.selectionEnd, 3)
@@ -180,30 +181,42 @@ suite('components/TextInput', () => {
}
})
test('prop "placeholder"')
test('prop "placeholder"', () => {
const placeholder = 'placeholder'
const result = findShallowPlaceholder(utils.shallowRender(<TextInput placeholder={placeholder} />))
assert.equal(result.props.children, placeholder)
})
test('prop "placeholderTextColor"')
test('prop "placeholderTextColor"', () => {
const placeholder = 'placeholder'
let result = findShallowPlaceholder(utils.shallowRender(<TextInput placeholder={placeholder} />))
assert.equal(result.props.style.color, 'darkgray')
result = findShallowPlaceholder(utils.shallowRender(<TextInput placeholder={placeholder} placeholderTextColor='red' />))
assert.equal(result.props.style.color, 'red')
})
test('prop "secureTextEntry"', () => {
let dom = utils.renderToDOM(<TextInput secureTextEntry />)
assert.equal(dom.getAttribute('type'), 'password')
let input = findInput(utils.renderToDOM(<TextInput secureTextEntry />))
assert.equal(input.getAttribute('type'), 'password')
// ignored for multiline
dom = utils.renderToDOM(<TextInput multiline secureTextEntry />)
assert.equal(dom.getAttribute('type'), undefined)
input = findInput(utils.renderToDOM(<TextInput multiline secureTextEntry />))
assert.equal(input.getAttribute('type'), undefined)
})
utils.testIfDocumentFocused('prop "selectTextOnFocus"', () => {
const text = 'Text'
// false
let dom = utils.renderAndInject(<TextInput defaultValue={text} />)
dom.focus()
assert.equal(dom.selectionEnd, 0)
assert.equal(dom.selectionStart, 0)
let input = findInput(utils.renderAndInject(<TextInput defaultValue={text} />))
input.focus()
assert.equal(input.selectionEnd, 0)
assert.equal(input.selectionStart, 0)
// true
dom = utils.renderAndInject(<TextInput defaultValue={text} selectTextOnFocus />)
dom.focus()
assert.equal(dom.selectionEnd, 4)
assert.equal(dom.selectionStart, 0)
input = findInput(utils.renderAndInject(<TextInput defaultValue={text} selectTextOnFocus />))
input.focus()
assert.equal(input.selectionEnd, 4)
assert.equal(input.selectionStart, 0)
})
test('prop "style"', () => {
@@ -218,7 +231,7 @@ suite('components/TextInput', () => {
test('prop "value"', () => {
const value = 'value'
const result = utils.shallowRender(<TextInput value={value} />)
assert.equal(result.props.value, value)
const input = findShallowInput(utils.shallowRender(<TextInput value={value} />))
assert.equal(input.props.value, value)
})
})

View File

@@ -3,27 +3,50 @@ import CoreComponent from '../CoreComponent'
import React, { PropTypes } from 'react'
import ReactDOM from 'react-dom'
import StyleSheet from '../../modules/StyleSheet'
import Text from '../Text'
import TextareaAutosize from 'react-textarea-autosize'
import TextInputStylePropTypes from './TextInputStylePropTypes'
import View from '../View'
const textInputStyleKeys = Object.keys(TextInputStylePropTypes)
const styles = StyleSheet.create({
initial: {
...View.defaultProps.style,
borderColor: 'black',
borderWidth: 1
},
input: {
appearance: 'none',
backgroundColor: 'transparent',
borderColor: 'black',
borderWidth: '1px',
borderWidth: 0,
boxSizing: 'border-box',
color: 'inherit',
flexGrow: 1,
font: 'inherit',
padding: 0
padding: 0,
zIndex: 1
},
placeholder: {
bottom: 0,
color: 'darkgray',
left: 0,
overflow: 'hidden',
position: 'absolute',
right: 0,
top: 0,
whiteSpace: 'pre'
}
})
class TextInput extends React.Component {
constructor(props, context) {
super(props, context)
this.state = { showPlaceholder: !props.value && !props.defaultValue }
}
static propTypes = {
accessibilityLabel: CoreComponent.propTypes.accessibilityLabel,
...View.propTypes,
autoComplete: PropTypes.bool,
autoFocus: PropTypes.bool,
clearTextOnFocus: PropTypes.bool,
@@ -61,20 +84,26 @@ class TextInput extends React.Component {
_onBlur(e) {
const { onBlur } = this.props
const value = e.target.value
this.setState({ showPlaceholder: value === '' })
if (onBlur) onBlur(e)
}
_onChange(e) {
const { onChange, onChangeText } = this.props
if (onChangeText) onChangeText(e.target.value)
const value = e.target.value
this.setState({ showPlaceholder: value === '' })
if (onChangeText) onChangeText(value)
if (onChange) onChange(e)
}
_onFocus(e) {
const { clearTextOnFocus, onFocus, selectTextOnFocus } = this.props
const node = ReactDOM.findDOMNode(this)
const node = ReactDOM.findDOMNode(this.refs.input)
const value = e.target.value
if (clearTextOnFocus) node.value = ''
if (selectTextOnFocus) node.select()
this.setState({ showPlaceholder: value === '' })
if (onFocus) onFocus(e)
}
@@ -92,7 +121,9 @@ class TextInput extends React.Component {
render() {
const {
/* eslint-disable react/prop-types */
accessibilityLabel,
/* eslint-enable react/prop-types */
autoComplete,
autoFocus,
defaultValue,
@@ -102,11 +133,9 @@ class TextInput extends React.Component {
maxNumberOfLines,
multiline,
numberOfLines,
onBlur,
onChange,
onChangeText,
onSelectionChange,
placeholder,
placeholderTextColor,
secureTextEntry,
style,
testID,
@@ -126,6 +155,10 @@ class TextInput extends React.Component {
case 'phone-pad':
type = 'tel'
break
case 'search':
case 'web-search':
type = 'search'
break
case 'url':
type = 'url'
break
@@ -136,23 +169,16 @@ class TextInput extends React.Component {
}
const propsCommon = {
accessibilityLabel,
autoComplete: autoComplete && 'on',
autoFocus,
className: 'TextInput',
defaultValue,
maxLength,
onBlur: onBlur && this._onBlur.bind(this),
onChange: (onChange || onChangeText) && this._onChange.bind(this),
onBlur: this._onBlur.bind(this),
onChange: this._onChange.bind(this),
onFocus: this._onFocus.bind(this),
onSelect: onSelectionChange && this._onSelectionChange.bind(this),
placeholder,
readOnly: !editable,
style: {
...styles.initial,
...resolvedStyle
},
testID,
style: { ...styles.input, outline: style.outline },
value
}
@@ -172,7 +198,26 @@ class TextInput extends React.Component {
const props = multiline ? propsMultiline : propsSingleline
return (
<CoreComponent {...props} />
<CoreComponent
accessibilityLabel={accessibilityLabel}
className='TextInput'
style={{
...styles.initial,
...resolvedStyle
}}
testID={testID}
>
<View style={{ flexGrow: 1 }}>
<CoreComponent {...props} ref='input' />
{placeholder && this.state.showPlaceholder && <Text
pointerEvents='none'
style={{
...styles.placeholder,
...(placeholderTextColor && { color: placeholderTextColor })
}}
>{placeholder}</Text>}
</View>
</CoreComponent>
)
}
}

View File

@@ -11,11 +11,7 @@ import TextInput from './components/TextInput'
import Touchable from './components/Touchable'
import View from './components/View'
export default React
export {
StyleSheet,
const ReactNative = {
// components
Image,
ListView,
@@ -23,5 +19,13 @@ export {
Text,
TextInput,
Touchable,
View
View,
// apis
StyleSheet,
// React
...React
}
module.exports = ReactNative

View File

@@ -3,6 +3,10 @@ import { PropTypes } from 'react'
const { number, string } = PropTypes
const numberOrString = PropTypes.oneOfType([ number, string ])
/**
* Any properties marked @private are used internally in resets or property
* mappings.
*/
export default {
alignContent: string,
alignItems: string,
@@ -38,20 +42,21 @@ export default {
borderRightWidth: numberOrString,
borderTopWidth: numberOrString,
bottom: numberOrString,
boxShadow: string,
boxSizing: string,
clear: string,
color: string,
cursor: string,
direction: string,
display: string,
flex: string,
direction: string, /* @private */
flex: string, /* @private */
flexBasis: string,
flexDirection: string,
flexGrow: numberOrString,
flexShrink: numberOrString,
flexWrap: string,
float: string,
font: string,
font: string, /* @private */
fontFamily: string,
fontSize: numberOrString,
fontStyle: string,
@@ -75,6 +80,7 @@ export default {
minWidth: numberOrString,
opacity: numberOrString,
order: numberOrString,
outline: string,
overflow: string,
overflowX: string,
overflowY: string,
@@ -93,9 +99,11 @@ export default {
textTransform: string,
top: numberOrString,
userSelect: string,
verticalAlign: string,
visibility: string,
whiteSpace: string,
width: numberOrString,
wordWrap: string,
writingDirection: string,
zIndex: numberOrString
}

View File

@@ -41,7 +41,7 @@ export default class Store {
const getCssSelector = (property, value) => {
let className = this.get(property, value)
if (!obfuscate && className) {
className = className.replace(/[:?.%\\$#]/g, '\\$&')
className = className.replace(/[,":?.%\\$#]/g, '\\$&')
}
return className
}

View File

@@ -82,8 +82,9 @@ suite('modules/StyleSheet/Store', () => {
test('replaces space characters', () => {
const store = new Store()
store.set('margin', '0 auto')
assert.deepEqual(store.get('margin', '0 auto'), 'margin:0-auto')
assert.equal(store.get('margin', '0 auto'), 'margin\:0-auto')
})
})
@@ -91,17 +92,17 @@ suite('modules/StyleSheet/Store', () => {
test('human-readable style sheet', () => {
const store = new Store()
store.set('alignItems', 'center')
store.set('color', '#fff')
store.set('fontFamily', '"Helvetica Neue", Arial, sans-serif')
store.set('marginBottom', 0)
store.set('margin', 1)
store.set('margin', 2)
store.set('margin', 3)
store.set('width', '100%')
const expected = '/* 5 unique declarations */\n' +
'.alignItems\\:center{align-items:center;}\n' +
'.margin\\:1px{margin:1px;}\n' +
'.margin\\:2px{margin:2px;}\n' +
'.margin\\:3px{margin:3px;}\n' +
'.marginBottom\\:0px{margin-bottom:0px;}'
'.color\\:\\#fff{color:#fff;}\n' +
'.fontFamily\\:\\"Helvetica-Neue\\"\\,-Arial\\,-sans-serif{font-family:"Helvetica Neue", Arial, sans-serif;}\n' +
'.marginBottom\\:0px{margin-bottom:0px;}\n' +
'.width\\:100\\%{width:100%;}'
assert.equal(store.toString(), expected)
})

View File

@@ -16,8 +16,9 @@ const fixture = {
backgroundSize: 'contain'
}
},
ignored: {
pading: 0
position: {
left: { left: 0 },
right: { right: 0 }
}
}
@@ -27,7 +28,9 @@ suite('modules/StyleSheet/getStyleObjects', () => {
assert.deepEqual(actual, [
{ margin: 0, padding: 0 },
{ backgroundSize: 'auto' },
{ backgroundSize: 'contain' }
{ backgroundSize: 'contain' },
{ left: 0 },
{ right: 0 }
])
})
})

View File

@@ -3,14 +3,27 @@
import assert from 'assert'
import isStyleObject from '../isStyleObject'
const style = { margin: 0 }
const notStyle = { root: style }
const styles = {
root: {
margin: 0
},
align: {
left: {
textAlign: 'left'
},
right: {
textAlign: 'right'
}
}
}
suite('modules/StyleSheet/isStyleObject', () => {
test('returns "true" for style objects', () => {
assert.ok(isStyleObject(style) === true)
})
test('returns "false" for non-style objects', () => {
assert.ok(isStyleObject(notStyle) === false)
assert.ok(isStyleObject(styles) === false)
assert.ok(isStyleObject(styles.align) === false)
})
test('returns "true" for style objects', () => {
assert.ok(isStyleObject(styles.root) === true)
assert.ok(isStyleObject(styles.align.left) === true)
})
})

View File

@@ -8,7 +8,8 @@ const styleShortHands = {
marginVertical: [ 'marginTop', 'marginBottom' ],
padding: [ 'paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft' ],
paddingHorizontal: [ 'paddingRight', 'paddingLeft' ],
paddingVertical: [ 'paddingTop', 'paddingBottom' ]
paddingVertical: [ 'paddingTop', 'paddingBottom' ],
writingDirection: [ 'direction' ]
}
/**

View File

@@ -1,9 +1,11 @@
import { pickProps } from '../filterObjectProps'
import StylePropTypes from '../StylePropTypes'
import isObject from './isObject'
const isStyleObject = (obj) => {
const declarations = pickProps(obj, Object.keys(StylePropTypes))
return Object.keys(declarations).length > 0
const values = Object.keys(obj).map((key) => obj[key])
for (let i = 0; i < values.length; i += 1) {
if (isObject(values[i])) { return false }
}
return true
}
export default isStyleObject

View File

@@ -6,7 +6,8 @@ export const resetCSS =
html {font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}
body {margin:0}
button::-moz-focus-inner, input::-moz-focus-inner {border:0;padding:0}
input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration {-webkit-appearance:none}`
input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration {-webkit-appearance:none}
ol,ul,li {list-style:none}`
/**
* Custom pointer event styles