Compare commits

..

12 Commits

Author SHA1 Message Date
Nicolas Gallagher
c65aa8a943 0.0.13 2015-12-27 12:05:35 +00:00
Nicolas Gallagher
0aa60a3c29 [fix] umd bundle 2015-12-27 12:04:40 +00:00
Nicolas Gallagher
8ac84f6da5 [change] StyleSheet: support code-splitting / export smaller API
Quick-fix for code-splitting support by updating the rendered style
sheet in place. Reduce the API to `create`, as the rest is now internal
to the framework.

Fix #34
2015-12-27 11:54:53 +00:00
Nicolas Gallagher
69166b1502 [fix] StyleSheet: support textAlign 'inherit' 2015-12-27 11:46:03 +00:00
Nicolas Gallagher
cc10de43eb [change] export or replace react-dom methods
This change adds the react-dom methods to the main export, since this is
a Web-only environment (React Native does something similar). It
augments the default render methods in order to move style sheet
management under the control of the library (necessary for
code-splitting support).

Relates to #52
2015-12-27 11:45:49 +00:00
Nicolas Gallagher
c850a5fa8c [add] CSS textShadow and reintroduce enums 2015-12-26 17:54:56 +00:00
Nicolas Gallagher
1efe5a533f [add] StyleSheet: support 'flex' style prop
Fix #63
2015-12-26 17:54:13 +00:00
Nicolas Gallagher
804132ce36 [fix] 'process.env.NODE_ENV' check
Use babel to transpile the source code without bundling it.
Use webpack to create a standalone, productionized UMD bundle.

Fix #50
2015-12-26 14:22:36 +00:00
Nicolas Gallagher
5335bcfd48 [chore] docs and formatting 2015-12-26 10:40:36 +00:00
Nicolas Gallagher
c0e7afc495 [change] Touchable: default prop values from RN Touchables 2015-12-22 00:15:48 +00:00
Nicolas Gallagher
fa88548c3c Update CONTRIBUTING and README with CodePen link 2015-12-21 23:50:58 +00:00
Nicolas Gallagher
39b2b2f979 Fix unreliable TextInput tests 2015-12-20 04:26:59 -08:00
29 changed files with 360 additions and 238 deletions

View File

@@ -1,25 +1,24 @@
# Contributing to this project
The issue tracker is the preferred channel for [bug reports](#bugs),
[features requests](#features) and [submitting pull
requests](#pull-requests).
[features requests](#features), and [submitting pull requests](#pull-requests).
<a name="bugs"></a>
## Bug reports
A bug is a _demonstrable problem_ that is caused by the code in the repository.
Good bug reports are extremely helpful - thank you!
Good bug reports are extremely helpful - thank you! You can compare the
behaviour against that expected with React Native by using the [React Native
Playground](https://rnplay.org/)
Guidelines for bug reports:
1. **Use the GitHub issue search** &mdash; check if the issue has already been
reported.
reported or fixed in `master`.
2. **Check if the issue has been fixed** &mdash; try to reproduce it using the
latest `master` or development branch in the repository.
3. **Isolate the problem** &mdash; create a [reduced test
case](http://css-tricks.com/reduced-test-cases/) and a live example.
2. **Isolate the problem** &mdash; create a [reduced test
case](http://css-tricks.com/reduced-test-cases/) using this
[codepen](https://codepen.io/necolas/pen/PZzwBR?editors=001).
A good bug report contains as much detail as possible. What is your
environment? What steps will reproduce the issue? What browser(s) and OS
@@ -49,9 +48,9 @@ Example:
## Feature requests
Feature requests are welcome. But take a moment to find out whether your idea
fits with the scope and aims of the project. It's up to *you* to make a strong
case to convince the project's developers of the merits of this feature. Please
provide as much detail and context as possible.
fits with the scope and aims of the project (i.e., is this for parity with
React Native? does it make sense on the Web?). Please provide as much detail
and context as you think is necessary to make your case.
<a name="pull-requests"></a>
@@ -70,7 +69,8 @@ Development commands:
* `npm run build` build the library
* `npm run examples` start the dev server and develop against live examples
* `npm run lint` run the linter
* `npm run test` run the linter and unit tests
* `npm run test:watch` run and watch the unit tests
* `npm test` run the linter and unit tests
Please follow this process for submitting a patch:

View File

@@ -2,10 +2,13 @@
[![Build Status][travis-image]][travis-url]
[![npm version][npm-image]][npm-url]
![gzipped size](https://img.shields.io/badge/gzipped-~18.6k-blue.svg)
![gzipped size](https://img.shields.io/badge/gzipped-~18.9k-blue.svg)
[React Native][react-native-url] components and APIs for the Web.
Try it out in the [React Native for Web
Playground](http://codepen.io/necolas/pen/PZzwBR) on CodePen.
* [Discord: #react-native-web on reactiflux][discord-url]
* [Gitter: react-native-web][gitter-url]
@@ -29,8 +32,9 @@ npm install --save react react-dom react-native-web
## Example
React Native for Web exports its components and a reference to the `React`
installation. Styles are defined with, and used as JavaScript objects.
React Native for Web exports its components, a reference to the `react`
installation, and the `react-dom` methods (customized for Web). Styles are defined
with, and used as JavaScript objects.
Component:
@@ -71,7 +75,7 @@ const styles = StyleSheet.create({
width: 40,
},
text: {
flex: 1,
flexGrow: 1,
justifyContent: 'center'
},
title: {
@@ -84,22 +88,20 @@ const styles = StyleSheet.create({
})
```
Pre-render styles on the server:
Pre-rendering on the server automatically includes your app styles:
```js
// server.js
import App from './components/App'
import React, { StyleSheet } from 'react-native-web'
import React from 'react-native-web'
const html = React.renderToString(<App />);
const css = StyleSheet.renderToString();
const Html = () => (
<html>
<head>
<meta charSet="utf-8" />
<meta content="initial-scale=1,width=device-width" name="viewport" />
<style id="react-stylesheet" dangerouslySetInnerHTML={{ __html: css } />
</head>
<body>
<div id="react-root" dangerouslySetInnerHTML={{ __html: html }} />
@@ -108,19 +110,15 @@ const Html = () => (
)
```
Render styles on the client:
Rendering on the client automatically includes your app styles and supports
progressive app loading (i.e. code-splitting / lazy bundle loading):
```js
// client.js
import App from './components/App'
import React, { StyleSheet } from 'react-native-web'
import ReactDOM from 'react-dom'
import React from 'react-native-web'
const reactRoot = document.getElementById('react-root')
const reactStyleSheet = document.getElementById('react-stylesheet')
ReactDOM.render(<App />, reactRoot)
reactStyleSheet.textContent = StyleSheet.renderToString()
React.render(<App />, document.getElementById('react-root'))
```
## APIs
@@ -174,7 +172,6 @@ flexbox][flexbox-guide-url].
### Media Queries, pseudo-classes, and pseudo-elements
Changing styles and/or the render tree in response to device adaptation can be
controlled in JavaScript, e.g.,
[react-media-queries](https://github.com/bloodyowl/react-media-queries),
@@ -185,7 +182,7 @@ benefit of co-locating breakpoint-specific DOM and style changes.
Pseudo-classes like `:hover` and `:focus` can be implemented with the `onHover`
and `onFocus` events.
Pseudo-elements are not supported.
Pseudo-elements are not supported; elements can be used instead.
## Accessibility

View File

@@ -1,43 +0,0 @@
var webpack = require('webpack')
var DedupePlugin = webpack.optimize.DedupePlugin
var DefinePlugin = webpack.DefinePlugin
var OccurenceOrderPlugin = webpack.optimize.OccurenceOrderPlugin
var UglifyJsPlugin = webpack.optimize.UglifyJsPlugin
var plugins = [
new DedupePlugin(),
new OccurenceOrderPlugin()
]
if (process.env.NODE_ENV === 'production') {
plugins.push(
new DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
})
)
plugins.push(
new UglifyJsPlugin({
compress: {
dead_code: true,
drop_console: true,
screw_ie8: true,
warnings: true
}
})
)
}
module.exports = {
module: {
loaders: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: 'babel-loader',
query: { cacheDirectory: true }
}
]
},
plugins: plugins
}

View File

@@ -1,17 +1,31 @@
var assign = require('object-assign')
var base = require('./webpack.config.base')
var constants = require('./constants')
var path = require('path')
var webpack = require('webpack')
module.exports = assign({}, base, {
module.exports = {
devServer: {
contentBase: constants.EXAMPLES_DIRECTORY
},
entry: {
example: path.join(constants.EXAMPLES_DIRECTORY, 'index')
example: constants.EXAMPLES_DIRECTORY
},
module: {
loaders: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
query: { cacheDirectory: true }
}
]
},
output: {
filename: 'examples.js',
path: constants.DIST_DIRECTORY
}
})
filename: 'examples.js'
},
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
}),
new webpack.optimize.DedupePlugin(),
new webpack.optimize.OccurenceOrderPlugin()
]
}

32
config/webpack.config.js Normal file
View File

@@ -0,0 +1,32 @@
var constants = require('./constants')
var webpack = require('webpack')
module.exports = {
entry: {
main: constants.DIST_DIRECTORY
},
externals: [{
'react': true,
'react-dom': true,
'react-dom/server': true
}],
output: {
filename: 'react-native-web.js',
library: 'ReactNativeWeb',
libraryTarget: 'umd',
path: constants.DIST_DIRECTORY
},
plugins: [
new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('production') }),
new webpack.optimize.DedupePlugin(),
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.optimize.UglifyJsPlugin({
compress: {
dead_code: true,
drop_console: true,
screw_ie8: true,
warnings: true
}
})
]
}

View File

@@ -1,19 +0,0 @@
var assign = require('object-assign')
var base = require('./webpack.config.base')
var constants = require('./constants')
module.exports = assign({}, base, {
entry: {
main: constants.SRC_DIRECTORY
},
externals: [{
'react': true,
'react-dom': true
}],
output: {
filename: 'react-native-web.js',
library: 'ReactNativeWeb',
libraryTarget: 'commonjs2',
path: constants.DIST_DIRECTORY
}
})

View File

@@ -37,24 +37,10 @@ Use styles:
</View>
```
Render styles on the server or in the browser:
```js
StyleSheet.renderToString()
```
## Methods
**create**(obj: {[key: string]: any})
**destroy**()
Clears all style information.
**renderToString**()
Renders a CSS Style Sheet.
## About
### Strategy
@@ -71,9 +57,9 @@ CSS](https://speakerdeck.com/vjeux/react-css-in-js):
6. Non-deterministic resolution
7. Breaking isolation
The strategy also minimizes the amount of generated CSS, making it more viable
to inline the style sheet when pre-rendering pages on the server. There is one
unique selector per unique style _declaration_.
The strategy minimizes the amount of generated CSS, making it viable to inline
the style sheet when pre-rendering pages on the server. There is one unique
selector per unique style _declaration_.
```js
// definition
@@ -88,7 +74,7 @@ unique selector per unique style _declaration_.
}
}
// css
// css output
//
// .a { color: gray; }
// .b { font-size: 2rem; }
@@ -130,16 +116,17 @@ In production the class names are obfuscated.
(CSS libraries like [Atomic CSS](http://acss.io/),
[Basscss](http://www.basscss.com/), [SUIT CSS](https://suitcss.github.io/), and
[tachyons](http://tachyons.io/) are attempts to limit style scope and limit
style sheet growth in a similar way. But they're CSS utility libraries, each with a
particular set of classes and features to learn. All of them require developers
to manually connect CSS classes for given styles.)
style sheet growth in a similar way. But they're CSS utility libraries, each
with a particular set of classes and features to learn. And all of them require
developers to manually connect CSS classes for given styles.)
### Reset
React Native for Web includes a very small CSS reset taken from
[normalize.css](https://necolas.github.io/normalize.css/). It removes unwanted
User Agent styles from (pseudo-)elements beyond the reach of React (e.g.,
`html`, `body`) or inline styles (e.g., `::-moz-focus-inner`).
[normalize.css](https://necolas.github.io/normalize.css/) **you do not need
to include normalize.css**. It removes unwanted User Agent styles from
(pseudo-)elements beyond the reach of React (e.g., `html`, `body`) or inline
styles (e.g., `::-moz-focus-inner`).
```css
html {
@@ -162,4 +149,10 @@ input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
ol,
ul,
li {
list-style:none
}
```

View File

@@ -61,6 +61,7 @@ This function is called on press.
+ `lineHeight`
+ `textAlign`
+ `textDecoration`
+ `textShadow`
+ `textTransform`
+ `whiteSpace`
+ `wordWrap`

View File

@@ -20,7 +20,7 @@ Unsupported React Native props:
Overrides the text that's read by the screen reader when the user interacts
with the element.
(web) **accessibilityRole**: oneOf(roles)
(web) **accessibilityRole**: oneOf(roles) = 'button'
Allows assistive technologies to present and support interaction with the view
in a manner that is consistent with user expectations for similar views of that
@@ -35,12 +35,12 @@ assistive technologies of a `role` value change.
When `false`, the view is hidden from screenreaders.
**activeOpacity**: number = 1
**activeOpacity**: number = 0.8
Sets the opacity of the child view when `onPressIn` is called. The opacity is
reset when `onPressOut` is called.
(web) **activeUnderlayColor**: string = 'transparent'
(web) **activeUnderlayColor**: string = 'black'
Sets the color of the background highlight when `onPressIn` is called. The
highlight is removed when `onPressOut` is called.
@@ -49,7 +49,7 @@ highlight is removed when `onPressOut` is called.
A single child element.
**delayLongPress**: number = 1000
**delayLongPress**: number = 500
Delay in ms, from `onPressIn`, before `onLongPress` is called.
@@ -59,7 +59,7 @@ Delay in ms, from `onPressIn`, before `onLongPress` is called.
Delay in ms, from the start of the touch, before `onPressIn` is called.
**delayPressOut**: number = 0
**delayPressOut**: number = 100
(TODO)

View File

@@ -100,6 +100,7 @@ from `style`.
+ `boxShadow`
+ `boxSizing`
+ `cursor`
+ `flex` (number)
+ `flexBasis`
+ `flexDirection`
+ `flexGrow`
@@ -108,7 +109,7 @@ from `style`.
+ `height`
+ `justifyContent`
+ `left`
+ `margin`
+ `margin` (single value)
+ `marginBottom`
+ `marginHorizontal`
+ `marginLeft`
@@ -124,7 +125,7 @@ from `style`.
+ `overflow`
+ `overflowX`
+ `overflowY`
+ `padding`
+ `padding` (single value)
+ `paddingBottom`
+ `paddingHorizontal`
+ `paddingLeft`

View File

@@ -2,7 +2,5 @@
<meta charset="utf-8">
<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 id="react-stylesheet"></style>
<div id="react-root"></div>
<script src="/examples.js"></script>

View File

@@ -2,8 +2,7 @@ import { MediaProvider, matchMedia } from 'react-media-queries'
import App from './components/App'
import createGetter from 'react-media-queries/lib/createMediaQueryGetter'
import createListener from 'react-media-queries/lib/createMediaQueryListener'
import React, { StyleSheet } from '../src'
import ReactDOM from 'react-dom'
import React from '../src'
const mediaQueries = {
small: '(min-width: 300px)',
@@ -12,11 +11,9 @@ const mediaQueries = {
}
const ResponsiveApp = matchMedia()(App)
ReactDOM.render(
React.render(
<MediaProvider getMedia={createGetter(mediaQueries)} listener={createListener(mediaQueries)}>
<ResponsiveApp />
</MediaProvider>,
document.getElementById('react-root')
)
document.getElementById('react-stylesheet').textContent = StyleSheet.renderToString()

View File

@@ -1,16 +1,17 @@
{
"name": "react-native-web",
"version": "0.0.12",
"version": "0.0.13",
"description": "React Native for Web",
"main": "dist/react-native-web.js",
"main": "dist/index.js",
"files": [
"dist"
],
"scripts": {
"build": "rm -rf ./dist && webpack --config config/webpack.config.publish.js --sort-assets-by --progress",
"build": "rm -rf ./dist && mkdir dist && babel src -d dist --ignore **/__tests__,src/modules/specHelpers",
"build:umd": "webpack --config config/webpack.config.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": "npm run build",
"prepublish": "npm run build && npm run build:umd",
"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"
@@ -22,20 +23,21 @@
"react-textarea-autosize": "^3.1.0"
},
"devDependencies": {
"babel-core": "^6.2.4",
"babel-cli": "^6.3.17",
"babel-core": "^6.3.13",
"babel-eslint": "^4.1.6",
"babel-loader": "^6.2.0",
"babel-preset-es2015": "^6.2.4",
"babel-preset-react": "^6.2.4",
"babel-preset-stage-1": "^6.2.4",
"babel-runtime": "^6.2.4",
"babel-preset-es2015": "^6.3.13",
"babel-preset-react": "^6.3.13",
"babel-preset-stage-1": "^6.3.13",
"babel-runtime": "^6.3.19",
"eslint": "^1.10.3",
"eslint-config-standard": "^4.4.0",
"eslint-config-standard-react": "^1.2.1",
"eslint-plugin-react": "^3.11.2",
"eslint-plugin-react": "^3.13.1",
"eslint-plugin-standard": "^1.3.1",
"karma": "^0.13.15",
"karma-browserstack-launcher": "^0.1.7",
"karma": "^0.13.16",
"karma-browserstack-launcher": "^0.1.8",
"karma-chrome-launcher": "^0.2.2",
"karma-firefox-launcher": "^0.1.7",
"karma-mocha": "^0.2.1",
@@ -44,7 +46,6 @@
"karma-webpack": "^1.7.0",
"mocha": "^2.3.4",
"node-libs-browser": "^0.5.3",
"object-assign": "^4.0.1",
"react": "^0.14.3",
"react-addons-test-utils": "^0.14.3",
"react-dom": "^0.14.3",

View File

@@ -0,0 +1,53 @@
/* eslint-env mocha */
import assert from 'assert'
import React from '..'
suite('ReactNativeWeb', () => {
suite('exports', () => {
test('React', () => {
assert.ok(React)
})
test('ReactDOM methods', () => {
assert.ok(React.findDOMNode)
assert.ok(React.render)
assert.ok(React.unmountComponentAtNode)
})
test('ReactDOM/server methods', () => {
assert.ok(React.renderToString)
assert.ok(React.renderToStaticMarkup)
})
})
suite('render methods', () => {
const id = 'test'
let div
setup(() => {
div = document.createElement('div')
div.id = id
document.body.appendChild(div)
})
teardown(() => {
document.body.removeChild(div)
})
test('"render" creates style sheet', () => {
React.render(<div />, div)
assert.ok(document.getElementById('react-stylesheet'))
})
test('"renderToString" creates style sheet', () => {
const result = React.renderToString(<div />)
assert.ok(result.indexOf('react-stylesheet') > -1)
})
test('"renderToStaticMarkup" creates style sheet', () => {
const result = React.renderToStaticMarkup(<div />)
assert.ok(result.indexOf('react-stylesheet') > -1)
})
})
})

View File

@@ -21,14 +21,11 @@ const roleComponents = {
class CoreComponent extends React.Component {
static propTypes = {
accessibilityLabel: PropTypes.string,
accessibilityLiveRegion: PropTypes.oneOf(['assertive', 'off', 'polite']),
accessibilityLiveRegion: PropTypes.oneOf([ 'assertive', 'off', 'polite' ]),
accessibilityRole: PropTypes.string,
accessible: PropTypes.bool,
className: PropTypes.string,
component: PropTypes.oneOfType([
PropTypes.func,
PropTypes.string
]),
component: PropTypes.oneOfType([ PropTypes.func, PropTypes.string ]),
style: PropTypes.object,
testID: PropTypes.string,
type: PropTypes.string

View File

@@ -201,10 +201,7 @@ class Image extends React.Component {
}}
testID={testID}
>
<img
src={displayImage}
style={styles.img}
/>
<img src={displayImage} style={styles.img} />
{children ? (
<View children={children} pointerEvents='box-none' style={styles.children} />
) : null}

View File

@@ -3,11 +3,12 @@ import ScrollView from '../ScrollView'
class ListView extends React.Component {
static propTypes = {
children: PropTypes.any
children: PropTypes.any,
style: PropTypes.style
}
static defaultProps = {
className: ''
style: {}
}
render() {

View File

@@ -14,6 +14,7 @@ export default {
'lineHeight',
'textAlign',
'textDecoration',
'textShadow',
'textTransform',
'whiteSpace',
'wordWrap',

View File

@@ -58,7 +58,7 @@ class Text extends React.Component {
...other
} = this.props
const className = `Text ${_className}`.trim()
const className = `${_className} Text`.trim()
const resolvedStyle = pickProps(style, textStyleKeys)
return (

View File

@@ -97,17 +97,14 @@ suite('components/TextInput', () => {
return str
}
let input = findInput(utils.renderAndInject(
const result = utils.shallowRender(
<TextInput
maxNumberOfLines={3}
multiline
value={generateValue()}
/>
))
const height = input.getBoundingClientRect().height
// need a range because of cross-browser differences
assert.ok(height >= 42, height)
assert.ok(height <= 48, height)
)
assert.equal(findShallowInput(result).props.maxRows, 3)
})
test('prop "multiline"', () => {
@@ -126,10 +123,14 @@ suite('components/TextInput', () => {
// with multiline
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 >= 30, height)
assert.ok(height <= 36, height)
const result = utils.shallowRender(
<TextInput
multiline
numberOfLines={3}
/>
)
assert.equal(findShallowInput(result).props.minRows, 3)
})
test('prop "onBlur"', (done) => {

View File

@@ -43,11 +43,11 @@ class Touchable extends React.Component {
static defaultProps = {
accessibilityRole: 'button',
activeOpacity: 1,
activeUnderlayColor: 'transparent',
delayLongPress: 1000,
activeOpacity: 0.8,
activeUnderlayColor: 'black',
delayLongPress: 500,
delayPressIn: 0,
delayPressOut: 0,
delayPressOut: 100,
style: styles.initial
}

View File

@@ -44,6 +44,7 @@ export default {
'boxShadow',
'boxSizing',
'cursor',
'flex',
'flexBasis',
'flexDirection',
'flexGrow',

View File

@@ -59,7 +59,7 @@ class View extends React.Component {
...other
} = this.props
const className = `View ${_className}`.trim()
const className = `${_className} View`.trim()
const pointerEventsStyle = pointerEvents && { pointerEvents }
const resolvedStyle = pickProps(style, viewStyleKeys)

View File

@@ -1,5 +1,8 @@
import React from 'react'
import ReactDOM from 'react-dom'
import ReactDOMServer from 'react-dom/server'
// api
import StyleSheet from './modules/StyleSheet'
// components
@@ -11,7 +14,35 @@ import TextInput from './components/TextInput'
import Touchable from './components/Touchable'
import View from './components/View'
const renderStyle = () => {
return `<style id='react-stylesheet'>${StyleSheet._renderToString()}</style>`
}
const render = (element, container, callback) => {
const styleElement = document.getElementById('react-stylesheet')
if (!styleElement) {
const style = renderStyle()
container.insertAdjacentHTML('beforebegin', style)
}
return ReactDOM.render(element, container, callback)
}
const renderToString = (element) => {
const style = renderStyle()
const html = ReactDOMServer.renderToString(element)
return `${style}\n${html}`
}
const renderToStaticMarkup = (element) => {
const style = renderStyle()
const html = ReactDOMServer.renderToStaticMarkup(element)
return `${style}\n${html}`
}
const ReactNative = {
// apis
StyleSheet,
// components
Image,
ListView,
@@ -21,11 +52,13 @@ const ReactNative = {
Touchable,
View,
// apis
StyleSheet,
// React
...React
...React,
...ReactDOM,
...ReactDOMServer,
render,
renderToString,
renderToStaticMarkup
}
module.exports = ReactNative

View File

@@ -1,23 +1,25 @@
import { PropTypes } from 'react'
const { number, string } = PropTypes
const numberOrString = PropTypes.oneOfType([ number, string ])
const { number, oneOf, oneOfType, string } = PropTypes
const numberOrString = oneOfType([ number, string ])
/**
* Any properties marked @private are used internally in resets or property
* mappings.
*
* https://developer.mozilla.org/en-US/docs/Web/CSS/Reference
*/
export default {
alignContent: string,
alignItems: string,
alignSelf: string,
alignContent: oneOf([ 'center', 'flex-end', 'flex-start', 'space-around', 'space-between', 'stretch' ]),
alignItems: oneOf([ 'baseline', 'center', 'flex-end', 'flex-start', 'stretch' ]),
alignSelf: oneOf([ 'auto', 'baseline', 'center', 'flex-end', 'flex-start', 'stretch' ]),
appearance: string,
backfaceVisibility: string,
backgroundAttachment: string,
backgroundAttachment: oneOf([ 'fixed', 'local', 'scroll' ]),
backgroundClip: string,
backgroundColor: string,
backgroundImage: string,
backgroundOrigin: string,
backgroundOrigin: oneOf([ 'border-box', 'content-box', 'padding-box' ]),
backgroundPosition: string,
backgroundRepeat: string,
backgroundSize: string,
@@ -43,26 +45,26 @@ export default {
borderTopWidth: numberOrString,
bottom: numberOrString,
boxShadow: string,
boxSizing: string,
boxSizing: oneOf([ 'border-box', 'content-box' ]),
clear: string,
color: string,
cursor: string,
display: string,
direction: string, /* @private */
flex: string, /* @private */
flex: number,
flexBasis: string,
flexDirection: string,
flexGrow: numberOrString,
flexShrink: numberOrString,
flexWrap: string,
float: string,
flexDirection: oneOf([ 'column', 'column-reverse', 'row', 'row-reverse' ]),
flexGrow: number,
flexShrink: number,
flexWrap: oneOf([ 'nowrap', 'wrap', 'wrap-reverse' ]),
float: oneOf([ 'left', 'none', 'right' ]),
font: string, /* @private */
fontFamily: string,
fontSize: numberOrString,
fontStyle: string,
fontWeight: string,
height: numberOrString,
justifyContent: string,
justifyContent: oneOf([ 'center', 'flex-end', 'flex-start', 'space-around', 'space-between' ]),
left: numberOrString,
letterSpacing: string,
lineHeight: numberOrString,
@@ -91,16 +93,17 @@ export default {
paddingRight: numberOrString,
paddingTop: numberOrString,
paddingVertical: numberOrString,
position: string,
position: oneOf([ 'absolute', 'fixed', 'relative', 'static' ]),
right: numberOrString,
textAlign: string,
textAlign: oneOf([ 'center', 'inherit', 'justify', 'justify-all', 'left', 'right' ]),
textDecoration: string,
textOverflow: string,
textTransform: string,
textShadow: string,
textTransform: oneOf([ 'capitalize', 'lowercase', 'none', 'uppercase' ]),
top: numberOrString,
userSelect: string,
verticalAlign: string,
visibility: string,
visibility: oneOf([ 'hidden', 'visible' ]),
whiteSpace: string,
width: numberOrString,
wordWrap: string,

View File

@@ -4,7 +4,7 @@ import assert from 'assert'
import expandStyle from '../expandStyle'
suite('modules/StyleSheet/expandStyle', () => {
test('style property', () => {
test('style resolution', () => {
const initial = {
borderTopWidth: 1,
borderWidth: 2,
@@ -13,7 +13,7 @@ suite('modules/StyleSheet/expandStyle', () => {
margin: 10
}
const expectedStyle = {
const expected = {
borderTopWidth: 1,
borderLeftWidth: 2,
borderRightWidth: 2,
@@ -24,6 +24,22 @@ suite('modules/StyleSheet/expandStyle', () => {
marginRight: 10
}
assert.deepEqual(expandStyle(initial), expectedStyle)
assert.deepEqual(expandStyle(initial), expected)
})
test('flex', () => {
const value = 10
const initial = {
flex: value
}
const expected = {
flexGrow: value,
flexShrink: 1,
flexBasis: 'auto'
}
assert.deepEqual(expandStyle(initial), expected)
})
})

View File

@@ -8,24 +8,40 @@ const styles = { root: { borderWidth: 1 } }
suite('modules/StyleSheet', () => {
setup(() => {
StyleSheet.destroy()
StyleSheet._destroy()
})
test('create', () => {
assert.equal(StyleSheet.create(styles), styles)
})
suite('create', () => {
const div = document.createElement('div')
test('renderToString', () => {
StyleSheet.create(styles)
assert.equal(
StyleSheet.renderToString(),
`${resetCSS}\n${predefinedCSS}\n` +
`/* 4 unique declarations */\n` +
`.borderBottomWidth\\:1px{border-bottom-width:1px;}\n` +
`.borderLeftWidth\\:1px{border-left-width:1px;}\n` +
`.borderRightWidth\\:1px{border-right-width:1px;}\n` +
`.borderTopWidth\\:1px{border-top-width:1px;}`
)
setup(() => {
document.body.appendChild(div)
StyleSheet.create(styles)
div.innerHTML = `<style id='react-stylesheet'>${StyleSheet._renderToString()}</style>`
})
teardown(() => {
document.body.removeChild(div)
})
test('returns styles object', () => {
assert.equal(StyleSheet.create(styles), styles)
})
test('updates already-rendered style sheet', () => {
StyleSheet.create({ root: { color: 'red' } })
assert.equal(
document.getElementById('react-stylesheet').textContent,
`${resetCSS}\n${predefinedCSS}\n` +
`/* 5 unique declarations */\n` +
`.borderBottomWidth\\:1px{border-bottom-width:1px;}\n` +
`.borderLeftWidth\\:1px{border-left-width:1px;}\n` +
`.borderRightWidth\\:1px{border-right-width:1px;}\n` +
`.borderTopWidth\\:1px{border-top-width:1px;}\n` +
`.color\\:red{color:red;}`
)
})
})
test('resolve', () => {
@@ -34,4 +50,17 @@ suite('modules/StyleSheet', () => {
StyleSheet.create(styles)
assert.deepEqual(StyleSheet.resolve(props), expected)
})
test('_renderToString', () => {
StyleSheet.create(styles)
assert.equal(
StyleSheet._renderToString(),
`${resetCSS}\n${predefinedCSS}\n` +
`/* 4 unique declarations */\n` +
`.borderBottomWidth\\:1px{border-bottom-width:1px;}\n` +
`.borderLeftWidth\\:1px{border-left-width:1px;}\n` +
`.borderRightWidth\\:1px{border-right-width:1px;}\n` +
`.borderTopWidth\\:1px{border-top-width:1px;}`
)
})
})

View File

@@ -42,6 +42,10 @@ const expandStyle = (style) => {
expandedProps.forEach((prop, i) => {
resolvedStyle[expandedProps[i]] = value
})
} else if (key === 'flex') {
resolvedStyle.flexGrow = value
resolvedStyle.flexShrink = 1
resolvedStyle.flexBasis = 'auto'
} else {
resolvedStyle[key] = value
}

View File

@@ -13,6 +13,24 @@ const initialState = { classNames: predefinedClassNames }
const options = { obfuscateClassNames: process.env.NODE_ENV === 'production' }
const createStore = () => new Store(initialState, options)
let store = createStore()
let isRendered = false
/**
* Destroy existing styles
*/
const _destroy = () => {
store = createStore()
isRendered = false
}
/**
* Render the styles as a CSS style sheet
*/
const _renderToString = () => {
const css = store.toString()
isRendered = true
return `${resetCSS}\n${predefinedCSS}\n${css}`
}
/**
* Process all unique declarations
@@ -33,24 +51,20 @@ const create = (styles: Object): Object => {
}
})
})
// update the style sheet in place
if (isRendered) {
const stylesheet = document.getElementById('react-stylesheet')
if (stylesheet) {
stylesheet.textContent = _renderToString()
} else if (process.env.NODE_ENV !== 'production') {
console.error('ReactNativeWeb: cannot find "react-stylesheet" element')
}
}
return styles
}
/**
* Destroy existing styles
*/
const destroy = () => {
store = createStore()
}
/**
* Render the styles as a CSS style sheet
*/
const renderToString = () => {
const css = store.toString()
return `${resetCSS}\n${predefinedCSS}\n${css}`
}
/**
* Accepts React props and converts inline styles to single purpose classes
* where possible.
@@ -81,8 +95,8 @@ const resolve = ({ className = '', style = {} }) => {
}
export default {
_destroy,
_renderToString,
create,
destroy,
renderToString,
resolve
}