diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 6d43d0ac..77206c8d 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -68,7 +68,7 @@ not want to merge into the project.
Development commands:
* `npm run build` – build the library
-* `npm run dev` – start the dev server and develop against live examples
+* `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
diff --git a/README.md b/README.md
index 3427d0c7..973bf132 100644
--- a/README.md
+++ b/README.md
@@ -3,8 +3,8 @@
[![Build Status][travis-image]][travis-url]
[![npm version][npm-image]][npm-url]
-The core [React Native][react-native-url] components adapted and expanded upon
-for the web, backed by a precomputed CSS library. ~21KB minified and gzipped.
+[React Native][react-native-url] components and APIs for the Web.
+~19 KB minified and gzipped.
* [Slack: reactiflux channel #react-native-web][slack-url]
* [Gitter: react-native-web][gitter-url]
@@ -12,7 +12,8 @@ for the web, backed by a precomputed CSS library. ~21KB minified and gzipped.
## Table of contents
* [Install](#install)
-* [Use](#use)
+* [Example](#example)
+* [APIs](#APIs)
* [Components](#components)
* [Styling](#styling)
* [Contributing](#contributing)
@@ -25,32 +26,107 @@ for the web, backed by a precomputed CSS library. ~21KB minified and gzipped.
npm install --save react react-native-web
```
-## Use
+## Example
React Native for Web exports its components and a reference to the `React`
-installation. Styles are authored in JavaScript as plain objects.
+installation. Styles are defined with, and used as JavaScript objects.
+
+Component:
```js
-import React, { View } from 'react-native-web'
+import React, { Image, StyleSheet, Text, View } from 'react-native-web'
-class MyComponent extends React.Component {
+const Title = ({ children }) => {children}
+
+const Summary = ({ children }) => (
+
+ {children}
+
+)
+
+class App extends React.Component {
render() {
return (
-
+
+
+ React Native Web
+ Build high quality web apps using React
+
)
- }
-}
+ },
+})
-const styles = {
- root: {
- borderColor: 'currentcolor'
- borderWidth: '5px',
- flexDirection: 'row'
- height: '5em'
+const styles = StyleSheet.create({
+ row: {
+ flexDirection: 'row',
+ margin: 40
+ },
+ image: {
+ height: 40,
+ marginRight: 10,
+ width: 40,
+ },
+ text: {
+ flex: 1,
+ justifyContent: 'center'
+ },
+ title: {
+ fontSize: '1.25rem',
+ fontWeight: 'bold'
+ },
+ subtitle: {
+ fontSize: '1rem'
}
-}
+})
```
+Pre-render styles on the server:
+
+```js
+// server.js
+import App from './components/App'
+import React, { StyleSheet } from 'react-native-web'
+
+const html = React.renderToString();
+const css = StyleSheet.renderToString();
+
+const Html = () => (
+
+
+
+
+
+
+
+
+)
+```
+
+Render styles on the client:
+
+```js
+// client.js
+import App from './components/App'
+import React, { StyleSheet } from 'react-native-web'
+
+React.render(
+ ,
+ document.getElementById('react-root')
+)
+
+document.getElementById('stylesheet').textContent = StyleSheet.renderToString()
+```
+
+## APIs
+
+### [`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.
+
## Components
### [`Image`](docs/components/Image.md)
@@ -88,78 +164,14 @@ The fundamental UI building block using flexbox for layout.
## Styling
-React Native for Web provides a mechanism to declare all your styles in
-JavaScript within your components. The `View` component makes it easy to build
-common layouts with flexbox, such as stacked and nested boxes with margin
-and padding. See this [guide to flexbox][flexbox-guide-url].
+React Native for Web relies on styles being defined in JavaScript.
-Authoring `style` is no different to the existing use of inline styles in
-React, but most inline styles are converted to single-purpose class names. The
-current implementation includes 300+ precomputed CSS declarations (~4.5KB
-gzipped) that covers many common property-value pairs. A more sophisticated
-build-time implementation may produce a slightly larger CSS file for large
-apps, and fall back to fewer inline styles. Read more about the [styling
-strategy](docs/style.md).
+The `View` component makes it easy to build common layouts with flexbox, such
+as stacked and nested boxes with margin and padding. See this [guide to
+flexbox][flexbox-guide-url].
-```js
-import React, { Image, Text, View } from 'react-native-web'
-
-class App extends React.Component {
- render() {
- return (
-
-
-
-
- React Native Web
-
-
- Build high quality web apps using React
-
-
-
- )
- },
-})
-
-const styles = {
- row: {
- flexDirection: 'row',
- margin: 40
- },
- image: {
- height: 40,
- marginRight: 10,
- width: 40,
- },
- text: {
- flex: 1,
- justifyContent: 'center'
- },
- title: {
- fontSize: '1.25rem',
- fontWeight: 'bold'
- },
- subtitle: {
- fontSize: '1rem'
- }
-}
-```
-
-Combine and override style objects:
-
-```js
-import baseStyle from './baseStyle'
-
-const buttonStyle = {
- ...baseStyle,
- backgroundColor: '#333',
- color: '#fff'
-}
-```
+Styling components can be achieved with inline styles or the use of
+[StyleSheet](docs/apis/StyleSheet.md).
## Contributing
diff --git a/config/webpack.config.base.js b/config/webpack.config.base.js
index 95ffd64b..671950b1 100644
--- a/config/webpack.config.base.js
+++ b/config/webpack.config.base.js
@@ -25,14 +25,6 @@ if (process.env.NODE_ENV === 'production') {
module.exports = {
module: {
loaders: [
- {
- test: /\.css$/,
- loader: [
- 'style-loader',
- 'css-loader?module&localIdentName=[hash:base64:5]',
- 'autoprefixer-loader'
- ].join('!')
- },
{
test: /\.jsx?$/,
exclude: /node_modules/,
diff --git a/docs/apis/StyleSheet.md b/docs/apis/StyleSheet.md
new file mode 100644
index 00000000..fe6869a9
--- /dev/null
+++ b/docs/apis/StyleSheet.md
@@ -0,0 +1,117 @@
+# StyleSheet
+
+React Native for Web will automatically vendor-prefix styles applied to the
+libraries components. The `StyleSheet` abstraction converts predefined styles
+to CSS without a compile-time step. Some styles cannot be resolved outside of
+the render loop and are applied as inline styles.
+
+The `style`-to-`className` conversion strategy is optimized to minimize the
+amount of CSS required. Unique declarations are defined using "atomic" CSS – a
+unique class name for a unique declaration.
+
+React Native for Web includes a CSS reset to remove unwanted user agent styles
+from elements and pseudo-elements beyond the reach of React (e.g., `html` and
+`body`).
+
+Create a new StyleSheet:
+
+```
+const styles = StyleSheet.create({
+ container: {
+ borderRadius: 4,
+ borderWidth: 0.5,
+ borderColor: '#d6d7da',
+ },
+ title: {
+ fontSize: 19,
+ fontWeight: 'bold',
+ },
+ activeTitle: {
+ color: 'red',
+ },
+})
+```
+
+Use styles:
+
+```js
+
+
+
+```
+
+Render styles on the server or in the browser:
+
+```js
+StyleSheet.renderToString()
+```
+
+## Methods
+
+**create**(obj: {[key: string]: any})
+
+**renderToString**()
+
+## Strategy
+
+Mapping entire `style` objects to CSS rules can lead to increasingly large CSS
+files. Each new component adds new rules to the stylesheet.
+
+
+
+React Native for Web uses an alternative strategy: mapping declarations to
+declarations.
+
+For example:
+
+```js
+...
+
+const styles = StyleSheet.create({
+ root: {
+ background: 'transparent',
+ display: 'flex',
+ flexGrow: 1,
+ justifyContent: 'center'
+ }
+})
+```
+
+Yields (in development):
+
+```html
+...
+```
+
+And is backed by the following CSS:
+
+```css
+.background\:transparent {background:transparent;}
+.display\:flex {display:flex;}
+.flexGrow\:1 {flex-grow:1;}
+.justifyContext\:center {justify-content:center;}
+```
+
+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
+stylesheet 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.)
+
+## Media Queries, pseudo-classes, and pseudo-elements
+
+Media Queries in JavaScript can be used to modify the render tree and styles.
+This has the benefit of co-locating breakpoint-specific DOM and style changes.
+
+Pseudo-classes like `:hover` and `:focus` can be replaced with JavaScript
+events.
+
+Pseudo-elements are not supported.
diff --git a/docs/style.md b/docs/style.md
deleted file mode 100644
index 3da2d0f6..00000000
--- a/docs/style.md
+++ /dev/null
@@ -1,123 +0,0 @@
-# Styling strategy
-
-Using the `style` attribute would normally produce inline styles. There are
-several existing approaches to using the `style` attribute, some of which
-convert inline styles to static CSS:
-[jsxstyle](https://github.com/petehunt/jsxstyle),
-[react-free-style](https://github.com/blakeembrey/react-free-style/),
-[react-inline](https://github.com/martinandert/react-inline),
-[react-native](https://facebook.github.io/react-native/),
-[react-style](https://github.com/js-next/react-style),
-[stilr](https://github.com/kodyl/stilr).
-
-## Style syntax: native vs proprietary data structure
-
-React Native for Web diverges from React Native by using plain JS objects for
-styles:
-
-```js
-...
-
-const styles = {
- root: {
- background: 'transparent',
- display: 'flex',
- flexGrow: 1,
- justifyContent: 'center'
- }
-};
-```
-
-Most approaches to managing style in React introduce a proprietary data
-structure, often via an implementation of `Stylesheet.create`.
-
-```js
-...
-
-const styles = Stylesheet.create({
- root: {
- background: 'transparent',
- display: 'flex',
- flexGrow: 1,
- justifyContent: 'center'
- }
-});
-```
-
-## JS-to-CSS: conversion strategies
-
-Mapping entire `style` objects to CSS rules can lead to increasingly large CSS
-files. Each new component adds new rules to the stylesheet.
-
-
-
-One strategy for converting styles from JS to CSS is to map style objects to
-CSS rules. Another strategy is to map declarations to declarations.
-
-React Native for Web currently includes a proof-of-concept implementation of
-the latter strategy. This results in smaller CSS files because all applications
-has fewer unique declarations than total declarations. Creating a new component
-with no new unique declarations results in no change to the CSS file.
-
-For example:
-
-```js
-...
-
-const styles = {
- root: {
- background: 'transparent',
- display: 'flex',
- flexGrow: 1,
- justifyContent: 'center'
- }
-};
-```
-
-Yields:
-
-```html
-...
-```
-
-And is backed by:
-
-```css
-._abcde { background: transparent }
-._fghij { display: flex }
-._klmno { flex-grow: 1 }
-._pqrst { justify-content: center }
-```
-
-The current implementation uses a precomputed CSS library of single-declaration
-rules, with obfuscated selectors. This handles a signficant portion of possible
-declarations. A build-time implementation would produce more accurate CSS
-files and fall through to inline styles significantly less often.
-
-(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
-stylesheet 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.)
-
-## Dynamic styles: use inline styles
-
-Some styles cannot be resolved ahead of time and continue to rely on inline
-styles:
-
-```js
- 0.5 ? 'red' : 'black') }}>...
-```
-
-## Media Queries, pseudo-classes, and pseudo-elements
-
-Media Queries could be replaced with `mediaMatch`. This would have the added
-benefit of co-locating breakpoint-specific DOM and style changes. Perhaps Media
-Query data could be accessed on `this.content`?
-
-Pseudo-classes like `:hover` and `:focus` can be handled with JavaScript.
-
-Pseudo-elements should be avoided in general, but for particular cases like
-`::placeholder` it might be necessary to reimplement it in the `TextInput`
-component (see React Native's API).
diff --git a/package.json b/package.json
index 9cf96450..e91f700a 100644
--- a/package.json
+++ b/package.json
@@ -8,7 +8,7 @@
],
"scripts": {
"build": "rm -rf ./dist && webpack --config config/webpack.config.publish.js --sort-assets-by --progress",
- "dev": "webpack-dev-server --config config/webpack.config.example.js --inline --colors --quiet",
+ "examples": "webpack-dev-server --config config/webpack.config.example.js --inline --hot --colors --quiet",
"lint": "eslint config src",
"prepublish": "NODE_ENV=publish npm run build",
"test": "npm run lint && npm run test:unit",
@@ -16,24 +16,22 @@
"test:watch": "npm run test:unit -- --no-single-run"
},
"dependencies": {
+ "inline-style-prefixer": "^0.3.3",
"react": ">=0.13.3",
"react-swipeable": "^3.0.2",
"react-tappable": "^0.6.0",
"react-textarea-autosize": "^2.5.3"
},
"devDependencies": {
- "autoprefixer-loader": "^3.1.0",
"babel-core": "^5.8.23",
"babel-eslint": "^4.1.1",
"babel-loader": "^5.3.2",
"babel-runtime": "^5.8.20",
- "css-loader": "^0.18.0",
"eslint": "^1.3.1",
"eslint-config-standard": "^4.3.1",
"eslint-config-standard-react": "^1.0.4",
"eslint-plugin-react": "^3.3.1",
"eslint-plugin-standard": "^1.3.0",
- "extract-text-webpack-plugin": "^0.8.2",
"karma": "^0.13.9",
"karma-browserstack-launcher": "^0.1.5",
"karma-chrome-launcher": "^0.2.0",
@@ -45,7 +43,6 @@
"mocha": "^2.3.0",
"node-libs-browser": "^0.5.2",
"object-assign": "^4.0.1",
- "style-loader": "^0.12.3",
"webpack": "^1.12.1",
"webpack-dev-server": "^1.10.1"
},
@@ -54,5 +51,14 @@
"repository": {
"type": "git",
"url": "git://github.com/necolas/react-native-web.git"
- }
+ },
+ "tags": [
+ "react"
+ ],
+ "keywords": [
+ "react",
+ "react-component",
+ "react-native",
+ "web"
+ ]
}
diff --git a/src/components/CoreComponent/index.js b/src/components/CoreComponent/index.js
index 3798917c..dd88989e 100644
--- a/src/components/CoreComponent/index.js
+++ b/src/components/CoreComponent/index.js
@@ -1,6 +1,6 @@
import React, { PropTypes } from 'react'
-import restyle from './modules/restyle'
-import stylePropTypes from './modules/stylePropTypes'
+import StylePropTypes from '../../modules/StylePropTypes'
+import StyleSheet from '../../modules/StyleSheet'
class CoreComponent extends React.Component {
static propTypes = {
@@ -13,18 +13,15 @@ class CoreComponent extends React.Component {
testID: PropTypes.string
}
- static stylePropTypes = stylePropTypes;
-
static defaultProps = {
- className: '',
component: 'div'
}
+ static stylePropTypes = StylePropTypes;
+
render() {
const {
- className,
component: Component,
- style,
testID,
...other
} = this.props
@@ -32,7 +29,7 @@ class CoreComponent extends React.Component {
return (
)
diff --git a/src/components/CoreComponent/modules/autoprefix.js b/src/components/CoreComponent/modules/autoprefix.js
deleted file mode 100644
index 6216a7a3..00000000
--- a/src/components/CoreComponent/modules/autoprefix.js
+++ /dev/null
@@ -1,47 +0,0 @@
-export default function prefixStyles(style) {
- if (style.hasOwnProperty('flexBasis')) {
- style = {
- WebkitFlexBasis: style.flexBasis,
- msFlexBasis: style.flexBasis,
- ...style
- }
- }
-
- if (style.hasOwnProperty('flexGrow')) {
- style = {
- WebkitBoxFlex: style.flexGrow,
- WebkitFlexGrow: style.flexGrow,
- msFlexPositive: style.flexGrow,
- ...style
- }
- }
-
- if (style.hasOwnProperty('flexShrink')) {
- style = {
- WebkitFlexShrink: style.flexShrink,
- msFlexNegative: style.flexShrink,
- ...style
- }
- }
-
- // NOTE: adding `;` to the string value prevents React from automatically
- // adding a `px` suffix to the unitless value
- if (style.hasOwnProperty('order')) {
- style = {
- WebkitBoxOrdinalGroup: `${parseInt(style.order, 10) + 1};`,
- WebkitOrder: `${style.order}`,
- msFlexOrder: `${style.order}`,
- ...style
- }
- }
-
- if (style.hasOwnProperty('transform')) {
- style = {
- WebkitTransform: style.transform,
- msTransform: style.transform,
- ...style
- }
- }
-
- return style
-}
diff --git a/src/components/CoreComponent/modules/restyle.js b/src/components/CoreComponent/modules/restyle.js
deleted file mode 100644
index c6375395..00000000
--- a/src/components/CoreComponent/modules/restyle.js
+++ /dev/null
@@ -1,40 +0,0 @@
-import autoprefix from './autoprefix'
-import styles from '../../../modules/styles'
-
-/**
- * Get the HTML class that corresponds to a style declaration
- * @param prop {string} prop name
- * @param style {Object} style
- * @return {string} class name
- */
-function getSinglePurposeClassName(prop, style) {
- const className = `${prop}-${style[prop]}`
- if (style.hasOwnProperty(prop) && styles[className]) {
- return styles[className]
- }
-}
-
-/**
- * Replace inline styles with single purpose classes where possible
- * @param props {Object} React Element properties
- * @return {Object}
- */
-export default function stylingStrategy(props) {
- let className
- let style = {}
-
- const classList = [ props.className ]
- for (const prop in props.style) {
- const styleClass = getSinglePurposeClassName(prop, props.style)
- if (styleClass) {
- classList.push(styleClass)
- } else {
- style[prop] = props.style[prop]
- }
- }
-
- className = classList.join(' ')
- style = autoprefix(style)
-
- return { className, style }
-}
diff --git a/src/components/Image/index.js b/src/components/Image/index.js
index f8845fae..50ecccf2 100644
--- a/src/components/Image/index.js
+++ b/src/components/Image/index.js
@@ -1,5 +1,6 @@
/* global window */
import { pickProps } from '../../modules/filterObjectProps'
+import StyleSheet from '../../modules/StyleSheet'
import CoreComponent from '../CoreComponent'
import ImageStylePropTypes from './ImageStylePropTypes'
import React, { PropTypes } from 'react'
@@ -13,7 +14,7 @@ const STATUS_IDLE = 'IDLE'
const imageStyleKeys = Object.keys(ImageStylePropTypes)
-const styles = {
+const styles = StyleSheet.create({
initial: {
alignSelf: 'flex-start',
backgroundColor: 'lightgray',
@@ -49,7 +50,7 @@ const styles = {
backgroundSize: '100% 100%'
}
}
-}
+})
class Image extends React.Component {
constructor(props, context) {
@@ -195,10 +196,10 @@ class Image extends React.Component {
accessible={accessible}
component='div'
style={{
- ...(styles.initial),
+ ...styles.initial,
...resolvedStyle,
...(backgroundImage && { backgroundImage }),
- ...(styles.resizeMode[resizeMode])
+ ...styles.resizeMode[resizeMode]
}}
testID={testID}
>
diff --git a/src/components/Text/index.js b/src/components/Text/index.js
index 1707dc44..b999d994 100644
--- a/src/components/Text/index.js
+++ b/src/components/Text/index.js
@@ -1,11 +1,12 @@
import { pickProps } from '../../modules/filterObjectProps'
import CoreComponent from '../CoreComponent'
import React, { PropTypes } from 'react'
+import StyleSheet from '../../modules/StyleSheet'
import TextStylePropTypes from './TextStylePropTypes'
const textStyleKeys = Object.keys(TextStylePropTypes)
-const styles = {
+const styles = StyleSheet.create({
initial: {
color: 'inherit',
display: 'inline-block',
@@ -20,7 +21,7 @@ const styles = {
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}
-}
+})
class Text extends React.Component {
static propTypes = {
diff --git a/src/components/TextInput/index.js b/src/components/TextInput/index.js
index 8774ccab..6c513959 100644
--- a/src/components/TextInput/index.js
+++ b/src/components/TextInput/index.js
@@ -1,12 +1,13 @@
import { pickProps } from '../../modules/filterObjectProps'
import CoreComponent from '../CoreComponent'
import React, { PropTypes } from 'react'
+import StyleSheet from '../../modules/StyleSheet'
import TextareaAutosize from 'react-textarea-autosize'
import TextInputStylePropTypes from './TextInputStylePropTypes'
const textInputStyleKeys = Object.keys(TextInputStylePropTypes)
-const styles = {
+const styles = StyleSheet.create({
initial: {
appearance: 'none',
backgroundColor: 'transparent',
@@ -17,7 +18,7 @@ const styles = {
font: 'inherit',
padding: 0
}
-}
+})
class TextInput extends React.Component {
static propTypes = {
diff --git a/src/components/Touchable/index.js b/src/components/Touchable/index.js
index 20fb756d..6240ca3d 100644
--- a/src/components/Touchable/index.js
+++ b/src/components/Touchable/index.js
@@ -1,14 +1,15 @@
import React, { PropTypes } from 'react'
import Tappable from 'react-tappable'
import View from '../View'
+import StyleSheet from '../../modules/StyleSheet'
-const styles = {
+const styles = StyleSheet.create({
initial: {
...View.defaultProps.style,
cursor: 'pointer',
userSelect: undefined
}
-}
+})
class Touchable extends React.Component {
constructor(props, context) {
diff --git a/src/components/View/index.js b/src/components/View/index.js
index 92a19ed9..2570bf92 100644
--- a/src/components/View/index.js
+++ b/src/components/View/index.js
@@ -1,11 +1,12 @@
import { pickProps } from '../../modules/filterObjectProps'
import CoreComponent from '../CoreComponent'
import React, { PropTypes } from 'react'
+import StyleSheet from '../../modules/StyleSheet'
import ViewStylePropTypes from './ViewStylePropTypes'
const viewStyleKeys = Object.keys(ViewStylePropTypes)
-const styles = {
+const styles = StyleSheet.create({
// https://github.com/facebook/css-layout#default-values
initial: {
alignItems: 'stretch',
@@ -26,7 +27,7 @@ const styles = {
font: 'inherit',
textAlign: 'inherit'
}
-}
+})
class View extends React.Component {
static propTypes = {
diff --git a/src/example.js b/src/example.js
index 1058459d..7b79a11d 100644
--- a/src/example.js
+++ b/src/example.js
@@ -1,12 +1,12 @@
-import React, { Image, Swipeable, Text, TextInput, Touchable, View } from '.'
+import React, { Image, StyleSheet, Swipeable, Text, TextInput, Touchable, View } from '.'
const { Component, PropTypes } = React
-class Heading extends Component {
+class Heading extends React.Component {
static propTypes = {
- children: Text.propTypes.children,
- level: PropTypes.oneOf(['1', '2', '3']),
- size: PropTypes.oneOf(['xlarge', 'large', 'normal'])
+ children: PropTypes.any,
+ level: PropTypes.string,
+ size: PropTypes.string
}
static defaultProps = {
@@ -27,7 +27,7 @@ class Heading extends Component {
}
}
-const headingStyles = {
+const headingStyles = StyleSheet.create({
size: {
xlarge: {
fontSize: '2rem',
@@ -44,7 +44,7 @@ const headingStyles = {
marginTop: '0.5em'
}
}
-}
+})
class Example extends Component {
static propTypes = {
@@ -205,7 +205,7 @@ class Example extends Component {
}
}
-const styles = {
+const styles = StyleSheet.create({
root: {
maxWidth: '600px',
margin: '0 auto'
@@ -226,7 +226,7 @@ const styles = {
pointerEventsBox: {
alignItems: 'center',
borderWidth: '1px',
- flexGrow: '1',
+ flexGrow: 1,
height: '100px',
justifyContent: 'center'
},
@@ -236,6 +236,8 @@ const styles = {
height: '200px',
justifyContent: 'center'
}
-}
+})
React.render(, document.getElementById('react-root'))
+
+document.getElementById('react-stylesheet').textContent = StyleSheet.renderToString()
diff --git a/src/index.html b/src/index.html
index 9ca34596..8c64bf03 100644
--- a/src/index.html
+++ b/src/index.html
@@ -4,5 +4,6 @@
+
diff --git a/src/index.js b/src/index.js
index e5f2bf13..0354b50b 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,5 +1,7 @@
import React from 'react'
+import StyleSheet from './modules/StyleSheet'
+
// components
import Image from './components/Image'
import ListView from './components/ListView'
@@ -13,6 +15,9 @@ import View from './components/View'
export default React
export {
+ StyleSheet,
+
+ // components
Image,
ListView,
ScrollView,
diff --git a/src/components/CoreComponent/modules/stylePropTypes.js b/src/modules/StylePropTypes/index.js
similarity index 81%
rename from src/components/CoreComponent/modules/stylePropTypes.js
rename to src/modules/StylePropTypes/index.js
index efe1afb9..fe226527 100644
--- a/src/components/CoreComponent/modules/stylePropTypes.js
+++ b/src/modules/StylePropTypes/index.js
@@ -1,11 +1,7 @@
import { PropTypes } from 'react'
-const numberOrString = PropTypes.oneOfType([
- PropTypes.number,
- PropTypes.string
-])
-
-const { string } = PropTypes
+const { number, string } = PropTypes
+const numberOrString = PropTypes.oneOfType([ number, string ])
export default {
alignContent: string,
@@ -20,21 +16,22 @@ export default {
backgroundPosition: string,
backgroundRepeat: string,
backgroundSize: string,
- borderColor: numberOrString,
- borderBottomColor: numberOrString,
- borderLeftColor: numberOrString,
- borderRightColor: numberOrString,
- borderTopColor: numberOrString,
+ border: string,
+ borderColor: string,
+ borderBottomColor: string,
+ borderLeftColor: string,
+ borderRightColor: string,
+ borderTopColor: string,
borderRadius: numberOrString,
borderTopLeftRadius: numberOrString,
borderTopRightRadius: numberOrString,
borderBottomLeftRadius: numberOrString,
borderBottomRightRadius: numberOrString,
- borderStyle: numberOrString,
- borderBottomStyle: numberOrString,
- borderLeftStyle: numberOrString,
- borderRightStyle: numberOrString,
- borderTopStyle: numberOrString,
+ borderStyle: string,
+ borderBottomStyle: string,
+ borderLeftStyle: string,
+ borderRightStyle: string,
+ borderTopStyle: string,
borderWidth: numberOrString,
borderBottomWidth: numberOrString,
borderLeftWidth: numberOrString,
@@ -47,6 +44,7 @@ export default {
cursor: string,
direction: string,
display: string,
+ flex: string,
flexBasis: string,
flexDirection: string,
flexGrow: numberOrString,
diff --git a/src/modules/StyleSheet/Store.js b/src/modules/StyleSheet/Store.js
new file mode 100644
index 00000000..bd14451a
--- /dev/null
+++ b/src/modules/StyleSheet/Store.js
@@ -0,0 +1,99 @@
+import hyphenate from './hyphenate'
+import normalizeValue from './normalizeValue'
+import prefixer from './prefixer'
+
+export default class Store {
+ constructor(
+ initialState:Object = {},
+ options:Object = { obfuscateClassNames: false }
+ ) {
+ this._counter = 0
+ this._classNames = { ...initialState.classNames }
+ this._declarations = { ...initialState.declarations }
+ this._options = options
+ }
+
+ get(property, value) {
+ const normalizedValue = normalizeValue(property, value)
+ const key = this._getDeclarationKey(property, normalizedValue)
+ return this._classNames[key]
+ }
+
+ set(property, value) {
+ if (value != null) {
+ const normalizedValue = normalizeValue(property, value)
+ const values = this._getPropertyValues(property) || []
+ if (values.indexOf(normalizedValue) === -1) {
+ values.push(normalizedValue)
+ this._setClassName(property, normalizedValue)
+ this._setPropertyValues(property, values)
+ }
+ }
+ }
+
+ toString() {
+ const obfuscate = this._options.obfuscateClassNames
+
+ // sort the properties to ensure shorthands are first in the cascade
+ const properties = Object.keys(this._declarations).sort()
+
+ // transform the class name to a valid CSS selector
+ const getCssSelector = (property, value) => {
+ let className = this.get(property, value)
+ if (!obfuscate && className) {
+ className = className.replace(/[:?.%\\$#]/g, '\\$&')
+ }
+ return className
+ }
+
+ // transform the declarations into CSS rules with vendor-prefixes
+ const buildCSSRules = (property, values) => {
+ return values.reduce((cssRules, value) => {
+ const declarations = prefixer.prefix({ [property]: value })
+ const cssDeclarations = Object.keys(declarations).reduce((str, prop) => {
+ str += `${hyphenate(prop)}:${value};`
+ return str
+ }, '')
+ const selector = getCssSelector(property, value)
+
+ cssRules += `\n.${selector}{${cssDeclarations}}`
+
+ return cssRules
+ }, '')
+ }
+
+ const css = properties.reduce((css, property) => {
+ const values = this._declarations[property]
+ css += buildCSSRules(property, values)
+ return css
+ }, '')
+
+ return (`/* ${this._counter} unique declarations */${css}`)
+ }
+
+ _getDeclarationKey(property, value) {
+ return `${property}:${value}`
+ }
+
+ _getPropertyValues(property) {
+ return this._declarations[property]
+ }
+
+ _setPropertyValues(property, values) {
+ this._declarations[property] = values.map(value => normalizeValue(property, value))
+ }
+
+ _setClassName(property, value) {
+ const key = this._getDeclarationKey(property, value)
+ const exists = !!this._classNames[key]
+ if (!exists) {
+ this._counter += 1
+ if (this._options.obfuscateClassNames) {
+ this._classNames[key] = `_rn_${this._counter}`
+ } else {
+ const val = `${value}`.replace(/\s/g, '-')
+ this._classNames[key] = `${property}:${val}`
+ }
+ }
+ }
+}
diff --git a/src/modules/StyleSheet/__tests__/getStyleObjects-test.js b/src/modules/StyleSheet/__tests__/getStyleObjects-test.js
new file mode 100644
index 00000000..600c5044
--- /dev/null
+++ b/src/modules/StyleSheet/__tests__/getStyleObjects-test.js
@@ -0,0 +1,33 @@
+/* eslint-env mocha */
+
+import assert from 'assert'
+import getStyleObjects from '../getStyleObjects'
+
+const fixture = {
+ rule: {
+ margin: 0,
+ padding: 0
+ },
+ nested: {
+ auto: {
+ backgroundSize: 'auto'
+ },
+ contain: {
+ backgroundSize: 'contain'
+ }
+ },
+ ignored: {
+ pading: 0
+ }
+}
+
+suite('modules/StyleSheet/getStyleObjects', () => {
+ test('returns only style objects', () => {
+ const actual = getStyleObjects(fixture)
+ assert.deepEqual(actual, [
+ { margin: 0, padding: 0 },
+ { backgroundSize: 'auto' },
+ { backgroundSize: 'contain' }
+ ])
+ })
+})
diff --git a/src/modules/StyleSheet/__tests__/hyphenate-test.js b/src/modules/StyleSheet/__tests__/hyphenate-test.js
new file mode 100644
index 00000000..980485d7
--- /dev/null
+++ b/src/modules/StyleSheet/__tests__/hyphenate-test.js
@@ -0,0 +1,13 @@
+/* eslint-env mocha */
+
+import assert from 'assert'
+import hyphenate from '../hyphenate'
+
+suite('modules/StyleSheet/hyphenate', () => {
+ test('style property', () => {
+ assert.equal(hyphenate('alignItems'), 'align-items')
+ })
+ test('vendor prefixed style property', () => {
+ assert.equal(hyphenate('WebkitAppearance'), '-webkit-appearance')
+ })
+})
diff --git a/src/modules/StyleSheet/__tests__/index-test.js b/src/modules/StyleSheet/__tests__/index-test.js
new file mode 100644
index 00000000..e2226667
--- /dev/null
+++ b/src/modules/StyleSheet/__tests__/index-test.js
@@ -0,0 +1,34 @@
+/* eslint-env mocha */
+
+import { resetCSS, predefinedCSS } from '../predefs'
+import assert from 'assert'
+import StyleSheet from '..'
+
+const styles = { root: { border: 0 } }
+
+suite('modules/StyleSheet', () => {
+ setup(() => {
+ StyleSheet.destroy()
+ })
+
+ test('create', () => {
+ assert.equal(StyleSheet.create(styles), styles)
+ })
+
+ test('renderToString', () => {
+ StyleSheet.create(styles)
+ assert.equal(
+ StyleSheet.renderToString(),
+ `${resetCSS}\n${predefinedCSS}\n` +
+ `/* 1 unique declarations */\n` +
+ `.border\\:0px{border:0px;}`
+ )
+ })
+
+ test('resolve', () => {
+ const props = { className: 'className', style: styles.root }
+ const expected = { className: 'className border:0px', style: {} }
+ StyleSheet.create(styles)
+ assert.deepEqual(StyleSheet.resolve(props), expected)
+ })
+})
diff --git a/src/modules/StyleSheet/__tests__/isObject-test.js b/src/modules/StyleSheet/__tests__/isObject-test.js
new file mode 100644
index 00000000..f8bb977a
--- /dev/null
+++ b/src/modules/StyleSheet/__tests__/isObject-test.js
@@ -0,0 +1,15 @@
+/* eslint-env mocha */
+
+import assert from 'assert'
+import isObject from '../isObject'
+
+suite('modules/StyleSheet/isObject', () => {
+ test('returns "true" for objects', () => {
+ assert.ok(isObject({}) === true)
+ })
+ test('returns "false" for non-objects', () => {
+ assert.ok(isObject(function () {}) === false)
+ assert.ok(isObject([]) === false)
+ assert.ok(isObject('') === false)
+ })
+})
diff --git a/src/modules/StyleSheet/__tests__/isStyleObject-test.js b/src/modules/StyleSheet/__tests__/isStyleObject-test.js
new file mode 100644
index 00000000..350ec666
--- /dev/null
+++ b/src/modules/StyleSheet/__tests__/isStyleObject-test.js
@@ -0,0 +1,16 @@
+/* eslint-env mocha */
+
+import assert from 'assert'
+import isStyleObject from '../isStyleObject'
+
+const style = { margin: 0 }
+const notStyle = { root: style }
+
+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)
+ })
+})
diff --git a/src/modules/StyleSheet/__tests__/normalizeValue-test.js b/src/modules/StyleSheet/__tests__/normalizeValue-test.js
new file mode 100644
index 00000000..5dee4d3e
--- /dev/null
+++ b/src/modules/StyleSheet/__tests__/normalizeValue-test.js
@@ -0,0 +1,13 @@
+/* eslint-env mocha */
+
+import assert from 'assert'
+import normalizeValue from '../normalizeValue'
+
+suite('modules/StyleSheet/normalizeValue', () => {
+ test('normalizes property values requiring units', () => {
+ assert.deepEqual(normalizeValue('margin', 0), '0px')
+ })
+ test('ignores unitless property values', () => {
+ assert.deepEqual(normalizeValue('flexGrow', 1), 1)
+ })
+})
diff --git a/src/modules/StyleSheet/__tests__/store-test.js b/src/modules/StyleSheet/__tests__/store-test.js
new file mode 100644
index 00000000..65436d18
--- /dev/null
+++ b/src/modules/StyleSheet/__tests__/store-test.js
@@ -0,0 +1,127 @@
+/* eslint-env mocha */
+
+import assert from 'assert'
+import Store from '../Store'
+
+suite('modules/StyleSheet/Store', () => {
+ suite('the constructor', () => {
+ test('initialState', () => {
+ const initialState = { classNames: { 'alignItems:center': '__classname__' } }
+ const store = new Store(initialState)
+ assert.deepEqual(store._classNames['alignItems:center'], '__classname__')
+ })
+ })
+
+ suite('#get', () => {
+ test('returns a declaration-specific className', () => {
+ const initialState = {
+ classNames: {
+ 'alignItems:center': '__expected__',
+ 'alignItems:flex-start': '__error__'
+ }
+ }
+ const store = new Store(initialState)
+ assert.deepEqual(store.get('alignItems', 'center'), '__expected__')
+ })
+ })
+
+ suite('#set', () => {
+ test('stores declarations', () => {
+ const store = new Store()
+ store.set('alignItems', 'center')
+ store.set('flexGrow', 0)
+ store.set('flexGrow', 1)
+ store.set('flexGrow', 2)
+ assert.deepEqual(store._declarations, {
+ alignItems: [ 'center' ],
+ flexGrow: [ 0, 1, 2 ]
+ })
+ })
+
+ test('human-readable classNames', () => {
+ const store = new Store()
+ store.set('alignItems', 'center')
+ store.set('flexGrow', 0)
+ store.set('flexGrow', 1)
+ store.set('flexGrow', 2)
+ assert.deepEqual(store._classNames, {
+ 'alignItems:center': 'alignItems:center',
+ 'flexGrow:0': 'flexGrow:0',
+ 'flexGrow:1': 'flexGrow:1',
+ 'flexGrow:2': 'flexGrow:2'
+ })
+ })
+
+ test('obfuscated classNames', () => {
+ const store = new Store({}, { obfuscateClassNames: true })
+ store.set('alignItems', 'center')
+ store.set('flexGrow', 0)
+ store.set('flexGrow', 1)
+ store.set('flexGrow', 2)
+ assert.deepEqual(store._classNames, {
+ 'alignItems:center': '_rn_1',
+ 'flexGrow:0': '_rn_2',
+ 'flexGrow:1': '_rn_3',
+ 'flexGrow:2': '_rn_4'
+ })
+ })
+
+ test('value normalization', () => {
+ const store = new Store()
+ store.set('flexGrow', 0)
+ store.set('margin', 0)
+ assert.deepEqual(store._declarations, {
+ flexGrow: [ 0 ],
+ margin: [ '0px' ]
+ })
+ assert.deepEqual(store._classNames, {
+ 'flexGrow:0': 'flexGrow:0',
+ 'margin:0px': 'margin:0px'
+ })
+ })
+
+ test('replaces space characters', () => {
+ const store = new Store()
+ store.set('margin', '0 auto')
+ assert.deepEqual(store.get('margin', '0 auto'), 'margin:0-auto')
+ })
+ })
+
+ suite('#toString', () => {
+ test('human-readable style sheet', () => {
+ const store = new Store()
+ store.set('alignItems', 'center')
+ store.set('marginBottom', 0)
+ store.set('margin', 1)
+ store.set('margin', 2)
+ store.set('margin', 3)
+
+ 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;}'
+
+ assert.equal(store.toString(), expected)
+ })
+
+ test('obfuscated style sheet', () => {
+ const store = new Store({}, { obfuscateClassNames: true })
+ store.set('alignItems', 'center')
+ store.set('marginBottom', 0)
+ store.set('margin', 1)
+ store.set('margin', 2)
+ store.set('margin', 3)
+
+ const expected = '/* 5 unique declarations */\n' +
+ '._rn_1{align-items:center;}\n' +
+ '._rn_3{margin:1px;}\n' +
+ '._rn_4{margin:2px;}\n' +
+ '._rn_5{margin:3px;}\n' +
+ '._rn_2{margin-bottom:0px;}'
+
+ assert.equal(store.toString(), expected)
+ })
+ })
+})
diff --git a/src/modules/StyleSheet/getStyleObjects.js b/src/modules/StyleSheet/getStyleObjects.js
new file mode 100644
index 00000000..ebec0aba
--- /dev/null
+++ b/src/modules/StyleSheet/getStyleObjects.js
@@ -0,0 +1,22 @@
+import isObject from './isObject'
+import isStyleObject from './isStyleObject'
+
+/**
+ * Recursively check for objects that are style rules.
+ */
+const getStyleObjects = (styles: Object): Array => {
+ const keys = Object.keys(styles)
+ return keys.reduce((rules, key) => {
+ const possibleRule = styles[key]
+ if (isObject(possibleRule)) {
+ if (isStyleObject(possibleRule)) {
+ rules.push(possibleRule)
+ } else {
+ rules = rules.concat(getStyleObjects(possibleRule))
+ }
+ }
+ return rules
+ }, [])
+}
+
+export default getStyleObjects
diff --git a/src/modules/StyleSheet/hyphenate.js b/src/modules/StyleSheet/hyphenate.js
new file mode 100644
index 00000000..fa12e660
--- /dev/null
+++ b/src/modules/StyleSheet/hyphenate.js
@@ -0,0 +1 @@
+export default (string) => string.replace(/([A-Z])/g, '-$1').toLowerCase()
diff --git a/src/modules/StyleSheet/index.js b/src/modules/StyleSheet/index.js
new file mode 100644
index 00000000..8345f1ba
--- /dev/null
+++ b/src/modules/StyleSheet/index.js
@@ -0,0 +1,75 @@
+import { resetCSS, predefinedCSS, predefinedClassNames } from './predefs'
+import getStyleObjects from './getStyleObjects'
+import prefixer from './prefixer'
+import Store from './Store'
+
+/**
+ * Initialize the store with pointer-event styles mapping to our custom pointer
+ * event classes
+ */
+const initialState = { classNames: predefinedClassNames }
+const options = { obfuscateClassNames: process.env.NODE_ENV === 'production' }
+const createStore = () => new Store(initialState, options)
+let store = createStore()
+
+/**
+ * Process all unique declarations
+ */
+const create = (styles: Object): Object => {
+ const rules = getStyleObjects(styles)
+ rules.forEach((rule) => {
+ Object.keys(rule).forEach(property => {
+ const value = rule[property]
+ // add each declaration to the store
+ store.set(property, value)
+ })
+ })
+ 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.
+ */
+const resolve = ({ className = '', style = {} }) => {
+ let _className
+ let _style = {}
+
+ const classList = [ className ]
+ for (const prop in style) {
+ let styleClass = store.get(prop, style[prop])
+
+ if (styleClass) {
+ classList.push(styleClass)
+ } else {
+ _style[prop] = style[prop]
+ }
+ }
+
+ _className = classList.join(' ')
+ _style = prefixer.prefix(_style)
+
+ return { className: _className, style: _style }
+}
+
+export default {
+ create,
+ destroy,
+ renderToString,
+ resolve
+}
diff --git a/src/modules/StyleSheet/isObject.js b/src/modules/StyleSheet/isObject.js
new file mode 100644
index 00000000..056792fd
--- /dev/null
+++ b/src/modules/StyleSheet/isObject.js
@@ -0,0 +1,5 @@
+const isObject = (obj) => {
+ return Object.prototype.toString.call(obj) === '[object Object]'
+}
+
+export default isObject
diff --git a/src/modules/StyleSheet/isStyleObject.js b/src/modules/StyleSheet/isStyleObject.js
new file mode 100644
index 00000000..939b6480
--- /dev/null
+++ b/src/modules/StyleSheet/isStyleObject.js
@@ -0,0 +1,9 @@
+import { pickProps } from '../filterObjectProps'
+import StylePropTypes from '../StylePropTypes'
+
+const isStyleObject = (obj) => {
+ const declarations = pickProps(obj, Object.keys(StylePropTypes))
+ return Object.keys(declarations).length > 0
+}
+
+export default isStyleObject
diff --git a/src/modules/StyleSheet/normalizeValue.js b/src/modules/StyleSheet/normalizeValue.js
new file mode 100644
index 00000000..c91e9698
--- /dev/null
+++ b/src/modules/StyleSheet/normalizeValue.js
@@ -0,0 +1,33 @@
+const unitlessNumbers = {
+ boxFlex: true,
+ boxFlexGroup: true,
+ columnCount: true,
+ flex: true,
+ flexGrow: true,
+ flexPositive: true,
+ flexShrink: true,
+ flexNegative: true,
+ fontWeight: true,
+ lineClamp: true,
+ lineHeight: true,
+ opacity: true,
+ order: true,
+ orphans: true,
+ widows: true,
+ zIndex: true,
+ zoom: true,
+ // SVG-related
+ fillOpacity: true,
+ strokeDashoffset: true,
+ strokeOpacity: true,
+ strokeWidth: true
+}
+
+const normalizeValues = (property, value) => {
+ if (!unitlessNumbers[property] && typeof value === 'number') {
+ value = `${value}px`
+ }
+ return value
+}
+
+export default normalizeValues
diff --git a/src/modules/StyleSheet/predefs.js b/src/modules/StyleSheet/predefs.js
new file mode 100644
index 00000000..7caafcb1
--- /dev/null
+++ b/src/modules/StyleSheet/predefs.js
@@ -0,0 +1,28 @@
+/**
+ * Reset unwanted styles beyond the control of React inline styles
+ */
+export const resetCSS =
+`/* React Native Web */
+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}`
+
+/**
+ * Custom pointer event styles
+ */
+export const predefinedCSS =
+`/* pointer-events */
+._rn_pe-a {pointer-events:auto}
+._rn_pe-bn {pointer-events:none}
+._rn_pe-bn * {pointer-events:auto}
+._rn_pe-bo {pointer-events:auto}
+._rn_pe-bo * {pointer-events:none}
+._rn_pe-n {pointer-events:none}`
+
+export const predefinedClassNames = {
+ 'pointerEvents:auto': '_rn_pe-a',
+ 'pointerEvents:box-none': '_rn_pe-bn',
+ 'pointerEvents:box-only': '_rn_pe-bo',
+ 'pointerEvents:none': '_rn_pe-n'
+}
diff --git a/src/modules/StyleSheet/prefixer.js b/src/modules/StyleSheet/prefixer.js
new file mode 100644
index 00000000..4292d803
--- /dev/null
+++ b/src/modules/StyleSheet/prefixer.js
@@ -0,0 +1,3 @@
+import Prefixer from 'inline-style-prefixer'
+const prefixer = new Prefixer()
+export default prefixer
diff --git a/src/modules/styles/index.js b/src/modules/styles/index.js
deleted file mode 100644
index b84a2a0b..00000000
--- a/src/modules/styles/index.js
+++ /dev/null
@@ -1,2 +0,0 @@
-import styles from './styles.css'
-export default styles
diff --git a/src/modules/styles/styles.css b/src/modules/styles/styles.css
deleted file mode 100644
index 7a0347d8..00000000
--- a/src/modules/styles/styles.css
+++ /dev/null
@@ -1,688 +0,0 @@
-/* align-content */
-
-.alignContent-center { align-content: center; }
-.alignContent-flex-end { align-content: flex-end; }
-.alignContent-flex-start { align-content: flex-start; }
-.alignContent-stretch { align-content: stretch; }
-.alignContent-space-around { align-content: space-around; }
-.alignContent-space-between { align-content: space-between; }
-
-/* align-items */
-
-.alignItems-center { align-items: center; }
-.alignItems-flex-end { align-items: flex-end; }
-.alignItems-flex-start { align-items: flex-start; }
-.alignItems-stretch { align-items: stretch; }
-.alignItems-space-around { align-items: space-around; }
-.alignItems-space-between { align-items: space-between; }
-
-/* align-self */
-
-.alignSelf-auto { align-self: auto; }
-.alignSelf-baseline { align-self: baseline; }
-.alignSelf-center { align-self: center; }
-.alignSelf-flex-end { align-self: flex-end; }
-.alignSelf-flex-start { align-self: flex-start; }
-.alignSelf-stretch { align-self: stretch; }
-
-/* appearance */
-
-.appearance-none { appearance: none; }
-
-/* background-attachment */
-
-.backgroundAttachment-fixed { background-attachment: fixed; }
-.backgroundAttachment-inherit { background-attachment: inherit; }
-.backgroundAttachment-local { background-attachment: local; }
-.backgroundAttachment-scroll { background-attachment: scroll; }
-
-/* background-clip */
-
-.backgroundClip-border-box { background-clip: border-box; }
-.backgroundClip-content-box { background-clip: content-box; }
-.backgroundClip-inherit { background-clip: inherit; }
-.backgroundClip-padding-box { background-clip: padding-box; }
-
-/* background-color */
-
-.backgroundColor-\#000,
-.backgroundColor-black { background-color: black; }
-.backgroundColor-\#fff,
-.backgroundColor-white { background-color: white; }
-.backgroundColor-currentcolor,
-.backgroundColor-currentColor { background-color: currentcolor; }
-.backgroundColor-inherit { background-color: inherit; }
-.backgroundColor-transparent { background-color: transparent; }
-
-/* background-image */
-
-.backgroundImage { background-image: none; }
-
-/* background-origin */
-
-.backgroundOrigin-border-box { background-clip: border-box; }
-.backgroundOrigin-content-box { background-clip: content-box; }
-.backgroundOrigin-inherit { background-clip: inherit; }
-.backgroundOrigin-padding-box { background-clip: padding-box; }
-
-/* background-position */
-
-.backgroundPosition-bottom { background-position: bottom; }
-.backgroundPosition-center { background-position: center; }
-.backgroundPosition-left { background-position: left; }
-.backgroundPosition-right { background-position: right; }
-.backgroundPosition-top { background-position: top; }
-
-/* background-repeat */
-
-.backgroundRepeat-inherit { background-repeat: inherit; }
-.backgroundRepeat-no-repeat { background-repeat: no-repeat; }
-.backgroundRepeat-repeat { background-repeat: repeat; }
-.backgroundRepeat-repeat-x { background-repeat: repeat-x; }
-.backgroundRepeat-repeat-y { background-repeat: repeat-y; }
-.backgroundRepeat-round { background-repeat: round; }
-.backgroundRepeat-space { background-repeat: space; }
-
-/* background-size */
-
-.backgroundSize-auto { background-size: auto; }
-.backgroundSize-contain { background-size: contain; }
-.backgroundSize-cover { background-size: cover; }
-.backgroundSize-inherit { background-size: inherit; }
-
-/* border-color */
-
-.borderColor-\#fff,
-.borderColor-white { border-color: white; }
-.borderColor-currentcolor { border-color: currentcolor; }
-.borderColor-inherit { border-color: inherit; }
-.borderColor-transparent { border-color: transparent; }
-
-/* border-bottom-color */
-
-.borderBottomColor-\#fff,
-.borderBottomColor-white { border-bottom-color: white; }
-.borderBottomColor-currentcolor { border-bottom-color: currentcolor; }
-.borderBottomColor-inherit { border-bottom-color: inherit; }
-.borderBottomColor-transparent { border-bottom-color: transparent; }
-
-/* border-left-color */
-
-.borderLeftColor-\#fff,
-.borderLeftColor-white { border-left-color: white; }
-.borderLeftColor-currentcolor { border-left-color: currentcolor; }
-.borderLeftColor-inherit { border-left-color: inherit; }
-.borderLeftColor-transparent { border-left-color: transparent; }
-
-/* border-right-color */
-
-.borderRightColor-\#fff,
-.borderRightColor-white { border-right-color: white; }
-.borderRightColor-currentcolor { border-right-color: currentcolor; }
-.borderRightColor-inherit { border-right-color: inherit; }
-.borderRightColor-transparent { border-right-color: transparent; }
-
-/* border-top-color */
-
-.borderTopColor-\#fff,
-.borderTopColor-white { border-top-color: white; }
-.borderTopColor-currentcolor { border-top-color: currentcolor; }
-.borderTopColor-inherit { border-top-color: inherit; }
-.borderTopColor-transparent { border-top-color: transparent; }
-
-/* border-style */
-
-.borderStyle-dashed { border-style: dashed; }
-.borderStyle-dotted { border-style: dotted; }
-.borderStyle-inherit { border-style: inherit; }
-.borderStyle-none { border-style: none; }
-.borderStyle-solid { border-style: solid; }
-
-/* border-bottom-style */
-
-.borderBottomStyle-dashed { border-bottom-style: dashed; }
-.borderBottomStyle-dotted { border-bottom-style: dotted; }
-.borderBottomStyle-inherit { border-bottom-style: inherit; }
-.borderBottomStyle-none { border-bottom-style: none; }
-.borderBottomStyle-solid { border-bottom-style: solid; }
-
-/* border-left-style */
-
-.borderLeftStyle-dashed { border-left-style: dashed; }
-.borderLeftStyle-dotted { border-left-style: dotted; }
-.borderLeftStyle-inherit { border-left-style: inherit; }
-.borderLeftStyle-none { border-left-style: none; }
-.borderLeftStyle-solid { border-left-style: solid; }
-
-/* border-right-style */
-
-.borderRightStyle-dashed { border-right-style: dashed; }
-.borderRightStyle-dotted { border-right-style: dotted; }
-.borderRightStyle-inherit { border-right-style: inherit; }
-.borderRightStyle-none { border-right-style: none; }
-.borderRightStyle-solid { border-right-style: solid; }
-
-/* border-top-style */
-
-.borderTopStyle-dashed { border-top-style: dashed; }
-.borderTopStyle-dotted { border-top-style: dotted; }
-.borderTopStyle-inherit { border-top-style: inherit; }
-.borderTopStyle-none { border-top-style: none; }
-.borderTopStyle-solid { border-top-style: solid; }
-
-/* border-width */
-
-.borderWidth-0 { border-width: 0; }
-.borderWidth-1px { border-width: 1px; }
-.borderWidth-2px { border-width: 2px; }
-.borderWidth-3px { border-width: 3px; }
-.borderWidth-4px { border-width: 4px; }
-.borderWidth-5px { border-width: 5px; }
-
-/* border-bottom-width */
-
-.borderBottomWidth-0 { border-bottom-width: 0; }
-.borderBottomWidth-1px { border-bottom-width: 1px; }
-.borderBottomWidth-2px { border-bottom-width: 2px; }
-.borderBottomWidth-3px { border-bottom-width: 3px; }
-.borderBottomWidth-4px { border-bottom-width: 4px; }
-.borderBottomWidth-5px { border-bottom-width: 5px; }
-
-/* border-left-width */
-
-.borderLeftWidth-0 { border-left-width: 0; }
-.borderLeftWidth-1px { border-left-width: 1px; }
-.borderLeftWidth-2px { border-left-width: 2px; }
-.borderLeftWidth-3px { border-left-width: 3px; }
-.borderLeftWidth-4px { border-left-width: 4px; }
-.borderLeftWidth-5px { border-left-width: 5px; }
-
-/* border-right-width */
-
-.borderRightWidth-0 { border-right-width: 0; }
-.borderRightWidth-1px { border-right-width: 1px; }
-.borderRightWidth-2px { border-right-width: 2px; }
-.borderRightWidth-3px { border-right-width: 3px; }
-.borderRightWidth-4px { border-right-width: 4px; }
-.borderRightWidth-5px { border-right-width: 5px; }
-
-/* border-top-width */
-
-.borderTopWidth-0 { border-top-width: 0; }
-.borderTopWidth-1px { border-top-width: 1px; }
-.borderTopWidth-2px { border-top-width: 2px; }
-.borderTopWidth-3px { border-top-width: 3px; }
-.borderTopWidth-4px { border-top-width: 4px; }
-.borderTopWidth-5px { border-top-width: 5px; }
-
-/* bottom */
-
-.bottom-0 { bottom: 0; }
-.bottom-50% { bottom: 50%; }
-.bottom-100% { bottom: 100%; }
-
-/* box-sizing */
-
-.boxSizing-border-box { box-sizing: border-box; }
-.boxSizing-content-box { box-sizing: content-box; }
-.boxSizing-inherit { box-sizing: inherit; }
-.boxSizing-padding-box { box-sizing: padding-box; }
-
-/* clear */
-
-.clear-both { clear: both; }
-.clear-inherit { clear: inherit; }
-.clear-left { clear: left; }
-.clear-none { clear: none; }
-.clear-right { clear: right; }
-
-/* color */
-
-.color-#000,
-.color-black { color: black; }
-.color-\#fff,
-.color-white { color: white; }
-.color-inherit { color: inherit; }
-.color-transparent { color: transparent; }
-
-/* cursor */
-
-.cursor-default { cursor: default; }
-.cursor-pointer { cursor: pointer; }
-
-/* direction */
-
-.direction-inherit { direction: inherit; }
-.direction-ltr { direction: ltr; }
-.direction-rtl { direction: rtl; }
-
-/* display */
-
-.display-block { display: block; }
-.display-contents { display: contents; }
-.display-flex { display: flex; }
-.display-grid { display: grid; }
-.display-inherit { display: inherit; }
-.display-initial { display: initial; }
-.display-inline { display: inline; }
-.display-inline-block { display: inline-block; }
-.display-inline-flex { display: inline-flex; }
-.display-inline-grid { display: inline-grid; }
-.display-inline-table { display: inline-table; }
-.display-list-item { display: list-item; }
-.display-none { display: none; }
-.display-table { display: table; }
-.display-table-cell { display: table-cell; }
-.display-table-column { display: table-column; }
-.display-table-column-group { display: table-column-group; }
-.display-table-footer-group { display: table-footer-group; }
-.display-table-header-group { display: table-header-group; }
-.display-table-row { display: table-row; }
-.display-table-row-group { display: table-row-group; }
-.display-unset { display: unset; }
-
-/* flex-basis */
-
-.flexBasis-0 { flex-basis: 0%; }
-.flexBasis-auto { flex-basis: auto; }
-
-/* flex-direction */
-
-.flexDirection-column { flex-direction: column; }
-.flexDirection-column-reverse { flex-direction: column-reverse; }
-.flexDirection-row { flex-direction: row; }
-.flexDirection-row-reverse { flex-direction: row-reverse; }
-
-/* flex-grow */
-
-.flexGrow-0 { flex-grow: 0; }
-.flexGrow-1 { flex-grow: 1; }
-.flexGrow-2 { flex-grow: 2; }
-.flexGrow-3 { flex-grow: 3; }
-.flexGrow-4 { flex-grow: 4; }
-.flexGrow-5 { flex-grow: 5; }
-.flexGrow-6 { flex-grow: 6; }
-.flexGrow-7 { flex-grow: 7; }
-.flexGrow-8 { flex-grow: 8; }
-.flexGrow-9 { flex-grow: 9; }
-.flexGrow-10 { flex-grow: 10; }
-.flexGrow-11 { flex-grow: 11; }
-
-/* flex-shrink */
-
-.flexShrink-0 { flex-shrink: 0; }
-.flexShrink-1 { flex-shrink: 1; }
-.flexShrink-2 { flex-shrink: 2; }
-.flexShrink-3 { flex-shrink: 3; }
-.flexShrink-4 { flex-shrink: 4; }
-.flexShrink-5 { flex-shrink: 5; }
-.flexShrink-6 { flex-shrink: 6; }
-.flexShrink-7 { flex-shrink: 7; }
-.flexShrink-8 { flex-shrink: 8; }
-.flexShrink-9 { flex-shrink: 9; }
-.flexShrink-10 { flex-shrink: 10; }
-.flexShrink-11 { flex-shrink: 11; }
-
-/* flex-wrap */
-
-.flexWrap-nowrap { flex-wrap: nowrap; }
-.flexWrap-wrap { flex-wrap: wrap; }
-.flexWrap-wrap-reverse { flex-wrap: wrap-reverse; }
-
-/* float */
-
-.float-left { float: left; }
-.float-none { float: none; }
-.float-right { float: right; }
-
-/* font */
-
-.font-inherit { font: inherit; }
-
-/* font-family */
-
-.fontFamily-inherit { font-family: inherit; }
-.fontFamily-monospace { font-family: monospace; }
-.fontFamily-sans-serif { font-family: sans-serif; }
-.fontFamily-serif { font-family: serif; }
-
-/* font-size */
-
-.fontSize-100\% { font-size: 100%; }
-.fontSize-inherit { font-size: inherit; }
-
-/* font-style */
-
-.fontStyle-inherit { font-style: inherit; }
-.fontStyle-italic { font-style: italic; }
-.fontStyle-normal { font-style: normal; }
-.fontStyle-oblique { font-style: oblique; }
-
-/* font-weight */
-
-.fontWeight-100 { font-weight: 100; }
-.fontWeight-200 { font-weight: 200; }
-.fontWeight-300 { font-weight: 300; }
-.fontWeight-400 { font-weight: 400; }
-.fontWeight-500 { font-weight: 500; }
-.fontWeight-600 { font-weight: 600; }
-.fontWeight-700 { font-weight: 700; }
-.fontWeight-800 { font-weight: 800; }
-.fontWeight-900 { font-weight: 900; }
-.fontWeight-bold { font-weight: bold; }
-.fontWeight-bolder { font-weight: bolder; }
-.fontWeight-inherit { font-weight: inherit; }
-.fontWeight-lighter { font-weight: lighter; }
-.fontWeight-normal { font-weight: normal; }
-
-/* height */
-
-.height-0 { height: 0; }
-.height-10\% { height: 10%; }
-.height-12\.5\% { height: 12.5%; }
-.height-20\% { height: 20%; }
-.height-25\% { height: 25%; }
-.height-30\% { height: 30%; }
-.height-37\.5\% { height: 37.5%; }
-.height-40\% { height: 40%; }
-.height-50\% { height: 50%; }
-.height-60\% { height: 60%; }
-.height-62\.5\% { height: 62.5%; }
-.height-70\% { height: 70%; }
-.height-75\% { height: 75%; }
-.height-80\% { height: 80%; }
-.height-87\.5\% { height: 87.5%; }
-.height-90\% { height: 90%; }
-.height-100\% { height: 100%; }
-
-/* justify-content */
-
-.justifyContent-center { justify-content: center; }
-.justifyContent-flex-end { justify-content: flex-end; }
-.justifyContent-flex-start { justify-content: flex-start; }
-.justifyContent-space-around { justify-content: space-around; }
-.justifyContent-space-between { justify-content: space-between; }
-
-/* left */
-
-.left-0 { left: 0; }
-.left-50% { left: 50%; }
-.left-100% { left: 100%; }
-
-/* line-height */
-
-.lineHeight-inherit { line-height: inherit; }
-.lineHeight-normal { line-height: normal; }
-
-/* list-style */
-
-.listStyle-none { list-style: none; }
-
-/* margin */
-
-.margin-0 { margin: 0; }
-.margin-auto { margin: auto; }
-
-/* margin-bottom */
-
-.marginBottom-auto { margin-bottom: auto; }
-.marginBottom-0 { margin-bottom: 0; }
-.marginBottom-1em { margin-bottom: 1em; }
-.marginBottom-1rem { margin-bottom: 1rem; }
-
-/* margin-left */
-
-.marginLeft-auto { margin-left: auto; }
-.marginLeft-0 { margin-left: 0; }
-.marginLeft-1em { margin-left: 1em; }
-.marginLeft-1rem { margin-left: 1rem; }
-
-/* margin-right */
-
-.marginRight-auto { margin-right: auto; }
-.marginRight-0 { margin-right: 0; }
-.marginRight-1em { margin-right: 1em; }
-.marginRight-1rem { margin-right: 1rem; }
-
-/* margin-top */
-
-.marginTop-auto { margin-top: auto; }
-.marginTop-0 { margin-top: 0; }
-.marginTop-1em { margin-top: 1em; }
-.marginTop-1rem { margin-top: 1rem; }
-
-/* max-height */
-
-.maxHeight-100\% { max-height: 100%; }
-
-/* max-width */
-
-.maxWidth-100\% { max-width: 100%; }
-
-/* min-height */
-
-.minHeight-100\% { min-height: 100%; }
-
-/* min-width */
-
-.minWidth-100\% { min-width: 100%; }
-
-/* opacity */
-
-.opacity-0 { opacity: 0; }
-.opacity-0\.1 { opacity: 0.1; }
-.opacity-0\.2 { opacity: 0.2; }
-.opacity-0\.25 { opacity: 0.25; }
-.opacity-0\.3 { opacity: 0.3; }
-.opacity-0\.4 { opacity: 0.4; }
-.opacity-0\.5 { opacity: 0.5; }
-.opacity-0\.6 { opacity: 0.6; }
-.opacity-0\.7 { opacity: 0.7; }
-.opacity-0\.75 { opacity: 0.75; }
-.opacity-0\.8 { opacity: 0.8; }
-.opacity-0\.9 { opacity: 0.9; }
-.opacity-1 { opacity: 1; }
-
-/* order */
-
-.order--1 { order: -1; }
-.order-1 { order: 1; }
-.order-2 { order: 2; }
-.order-3 { order: 3; }
-.order-4 { order: 4; }
-.order-5 { order: 5; }
-.order-6 { order: 6; }
-.order-7 { order: 7; }
-.order-8 { order: 8; }
-.order-9 { order: 9; }
-.order-10 { order: 10; }
-.order-11 { order: 11; }
-
-/* overflow */
-
-.overflow-auto { overflow: auto; }
-.overflow-hidden { overflow: hidden; }
-.overflow-inherit { overflow: inherit; }
-.overflow-scroll { overflow: scroll; }
-.overflow-visible { overflow: visible; }
-
-/* overflow-x */
-
-.overflowX-auto { overflow-x: auto; }
-.overflowX-hidden { overflow-x: hidden; }
-.overflowX-inherit { overflow-x: inherit; }
-.overflowX-scroll { overflow-x: scroll; }
-.overflowX-visible { overflow-x: visible; }
-
-/* overflow-y */
-
-.overflowY-auto { overflow-y: auto; }
-.overflowY-hidden { overflow-y: hidden; }
-.overflowY-inherit { overflow-y: inherit; }
-.overflowY-scroll { overflow-y: scroll; }
-.overflowY-visible { overflow-y: visible; }
-
-/* padding */
-
-.padding-0 { padding: 0; }
-.padding-1em { padding: 1em; }
-.padding-1rem { padding: 1rem; }
-
-/* padding-bottom */
-
-.paddingBottom-0 { padding-bottom: 0; }
-.paddingBottom-1em { padding-bottom: 1em; }
-.paddingBottom-1rem { padding-bottom: 1rem; }
-
-/* padding-left */
-
-.paddingLeft-0 { padding-left: 0; }
-.paddingLeft-1em { padding-left: 1em; }
-.paddingLeft-1rem { padding-left: 1rem; }
-
-/* padding-right */
-
-.paddingRight-0 { padding-right: 0; }
-.paddingRight-1em { padding-right: 1em; }
-.paddingRight-1rem { padding-right: 1rem; }
-
-/* padding-top */
-
-.paddingTop-0 { padding-top: 0; }
-.paddingTop-1em { padding-top: 1em; }
-.paddingTop-1rem { padding-top: 1rem; }
-
-/* pointer-events */
-
-.pointerEvents-auto { pointer-events: auto; }
-.pointerEvents-none { pointer-events: none; }
-.pointerEvents-box-none { pointer-events: none; }
-.pointerEvents-box-none * { pointer-events: auto;}
-.pointerEvents-box-only { pointer-events: auto; }
-.pointerEvents-box-only * { pointer-events: none; }
-
-/* position */
-
-.position-absolute { position: absolute; }
-.position-fixed { position: fixed; }
-.position-relative { position: relative; }
-
-/* right */
-
-.right-0 { right: 0; }
-.right-50% { right: 50%; }
-.right-100% { right: 100%; }
-
-/* text-align */
-
-.textAlign-center { text-align: center; }
-.textAlign-end { text-align: end; }
-.textAlign-inherit { text-align: inherit; }
-.textAlign-left { text-align: left; }
-.textAlign-right { text-align: right; }
-.textAlign-justify { text-align: justify; }
-.textAlign-start { text-align: start; }
-
-/* text-decoration */
-
-.textDecoration-inherit { text-decoration: inherit; }
-.textDecoration-none { text-decoration: none; }
-.textDecoration-underline { text-decoration: underline; }
-
-/* text-overflow */
-
-.textOverflow-clip { text-overflow: clip; }
-.textOverflow-ellipsis { text-overflow: ellipsis; }
-
-/* text-rendering */
-
-.textRendering-auto { text-rendering: auto; }
-.textRendering-geometricPrecision { text-rendering: geometricPrecision; }
-.textRendering-inherit { text-rendering: inherit; }
-.textRendering-optimizeLegibility { text-rendering: optimizeLegibility; }
-.textRendering-optimizeSpeed { text-rendering: optimizeSpeed; }
-
-/* text-transform */
-
-.textTransform-capitalize { text-transform: capitalize; }
-.textTransform-lowercase { text-transform: lowercase; }
-.textTransform-none { text-transform: none; }
-.textTransform-uppercase { text-transform: uppercase; }
-
-/* top */
-
-.top-0 { top: 0; }
-.top-50% { top: 50%; }
-.top-100% { top: 100%; }
-
-/* unicode-bidi */
-
-.unicodeBidi-bidi-override { unicode-bidi: bidi-override; }
-.unicodeBidi-embed { unicode-bidi: embed; }
-.unicodeBidi-inherit { unicode-bidi: inherit; }
-.unicodeBidi-isolate { unicode-bidi: isolate; }
-.unicodeBidi-isolate-override { unicode-bidi: isolate-override; }
-.unicodeBidi-normal { unicode-bidi: normal; }
-.unicodeBidi-plaintext { unicode-bidi: plaintext; }
-
-/* user-select */
-
-.userSelect-all { user-select: all; }
-.userSelect-inherit { user-select: inherit; }
-.userSelect-none { user-select: none; }
-.userSelect-text { user-select: text; }
-
-/* visibility */
-
-.visibility-collapse { visibility: collapse; }
-.visibility-hidden { visibility: hidden; }
-.visibility-inherit { visibility: inherit; }
-.visibility-visible { visibility: visible; }
-
-/* white-space */
-
-.whiteSpace-normal { white-space: normal; }
-.whiteSpace-nowrap { white-space: nowrap; }
-.whiteSpace-pre { white-space: pre; }
-.whiteSpace-pre-line { white-space: pre-line; }
-.whiteSpace-pre-wrap { white-space: pre-wrap; }
-
-/* width */
-
-.width-0 { width: 0; }
-.width-10\% { width: 10%; }
-.width-12\.5\% { width: 12.5%; }
-.width-20\% { width: 20%; }
-.width-25\% { width: 25%; }
-.width-30\% { width: 30%; }
-.width-37\.5\% { width: 37.5%; }
-.width-40\% { width: 40%; }
-.width-50\% { width: 50%; }
-.width-60\% { width: 60%; }
-.width-62\.5\% { width: 62.5%; }
-.width-70\% { width: 70%; }
-.width-75\% { width: 75%; }
-.width-80\% { width: 80%; }
-.width-87\.5\% { width: 87.5%; }
-.width-90\% { width: 90%; }
-.width-100\% { width: 100%; }
-
-/* word-wrap */
-
-.wordWrap-break-word { word-wrap: break-word; }
-.wordWrap-normal { word-wrap: normal; }
-
-/* z-index */
-
-.zIndex--1 { z-index: -1; }
-.zIndex-0 { z-index: 0; }
-.zIndex-1 { z-index: 1; }
-.zIndex-2 { z-index: 2; }
-.zIndex-3 { z-index: 3; }
-.zIndex-4 { z-index: 4; }
-.zIndex-5 { z-index: 5; }
-.zIndex-6 { z-index: 6; }
-.zIndex-7 { z-index: 7; }
-.zIndex-8 { z-index: 8; }
-.zIndex-9 { z-index: 9; }
-.zIndex-10 { z-index: 10; }