diff --git a/docs/apis/StyleSheet.md b/docs/apis/StyleSheet.md
index 654a151a..14ee202e 100644
--- a/docs/apis/StyleSheet.md
+++ b/docs/apis/StyleSheet.md
@@ -15,17 +15,52 @@ Each key of the object passed to `create` must define a style object.
Flattens an array of styles into a single style object.
-**renderToString**: function
+**render**: function
-Returns a string of CSS used to style the application.
+Returns a React ``
-
export default function renderApplication(RootComponent: Component, initialProps: Object, rootTag: any) {
invariant(rootTag, 'Expect to have a valid rootTag, instead got ', rootTag)
- // insert style sheet if needed
- const styleElement = document.getElementById(StyleSheet.elementId)
- if (!styleElement) { rootTag.insertAdjacentHTML('beforebegin', styleAsTagString(renderStyleSheetToString())) }
-
const component = (
)
const html = ReactDOMServer.renderToString(component)
- const style = renderStyleSheetToString()
- const styleElement = styleAsElement(style)
- return { html, style, styleElement }
+ const styleElement = StyleSheet.render()
+ return { html, styleElement }
}
diff --git a/src/apis/StyleSheet/StyleSheetRegistry.js b/src/apis/StyleSheet/StyleSheetRegistry.js
deleted file mode 100644
index 984c9697..00000000
--- a/src/apis/StyleSheet/StyleSheetRegistry.js
+++ /dev/null
@@ -1,113 +0,0 @@
-/**
- * Copyright (c) 2016-present, Nicolas Gallagher.
- * Copyright (c) 2015-present, Facebook, Inc.
- * All rights reserved.
- *
- * @flow
- */
-
-import createReactStyleObject from './createReactStyleObject'
-import hyphenate from './hyphenate'
-import { predefinedClassNames } from './predefs'
-import prefixAll from 'inline-style-prefixer/static'
-
-let stylesCache = {}
-let uniqueID = 0
-
-const getCacheKey = (prop, value) => `${prop}:${value}`
-
-const createCssDeclarations = (style) => {
- return Object.keys(style).map((prop) => {
- const property = hyphenate(prop)
- const value = style[prop]
- if (Array.isArray(value)) {
- return value.reduce((acc, curr) => {
- acc += `${property}:${curr};`
- return acc
- }, '')
- } else {
- return `${property}:${value};`
- }
- }).sort().join('')
-}
-
-class StyleSheetRegistry {
- /* for testing */
- static _reset() {
- stylesCache = {}
- uniqueID = 0
- }
-
- static renderToString() {
- let str = `/* ${uniqueID} unique declarations */`
-
- return Object.keys(stylesCache).reduce((str, key) => {
- const id = stylesCache[key].id
- const style = stylesCache[key].style
- const declarations = createCssDeclarations(style)
- const rule = `\n.${id}{${declarations}}`
- str += rule
- return str
- }, str)
- }
-
- static registerStyle(style: Object): number {
- if (process.env.NODE_ENV !== 'production') {
- Object.freeze(style)
- }
-
- const reactStyleObject = createReactStyleObject(style)
-
- Object.keys(reactStyleObject).forEach((prop) => {
- const value = reactStyleObject[prop]
- const cacheKey = getCacheKey(prop, value)
- const exists = stylesCache[cacheKey] && stylesCache[cacheKey].id
- if (!exists) {
- const id = ++uniqueID
- // add new declaration to the store
- stylesCache[cacheKey] = {
- id: `__style${id}`,
- style: prefixAll({ [prop]: value })
- }
- }
- })
-
- return style
- }
-
- static getStyleAsNativeProps(styleSheetObject, canUseCSS = false) {
- const classList = []
- const reactStyleObject = createReactStyleObject(styleSheetObject)
- let style = {}
-
- for (const prop in reactStyleObject) {
- const value = reactStyleObject[prop]
- const cacheKey = getCacheKey(prop, value)
- let selector = stylesCache[cacheKey] && stylesCache[cacheKey].id || predefinedClassNames[cacheKey]
-
- if (selector && canUseCSS) {
- classList.push(selector)
- } else {
- style[prop] = reactStyleObject[prop]
- }
- }
-
- /**
- * React 15 removed undocumented support for fallback values in
- * inline-styles. For now, pick the last value and regress browser support
- * for CSS features like flexbox.
- */
- const vendorPrefixedStyle = Object.keys(prefixAll(style)).reduce((acc, prop) => {
- const value = style[prop]
- acc[prop] = Array.isArray(value) ? value[value.length - 1] : value
- return acc
- }, {})
-
- return {
- className: classList.join(' '),
- style: vendorPrefixedStyle
- }
- }
-}
-
-module.exports = StyleSheetRegistry
diff --git a/src/apis/StyleSheet/__tests__/StyleSheetRegistry-test.js b/src/apis/StyleSheet/__tests__/StyleSheetRegistry-test.js
deleted file mode 100644
index 44581302..00000000
--- a/src/apis/StyleSheet/__tests__/StyleSheetRegistry-test.js
+++ /dev/null
@@ -1,55 +0,0 @@
-/* eslint-env mocha */
-
-/**
- * Copyright (c) 2015-present, Facebook, Inc.
- * All rights reserved.
- *
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree. An additional grant
- * of patent rights can be found in the PATENTS file in the same directory.
- */
-
-import assert from 'assert'
-import StyleSheetRegistry from '../StyleSheetRegistry'
-
-suite('apis/StyleSheet/StyleSheetRegistry', () => {
- setup(() => {
- StyleSheetRegistry._reset()
- })
-
- test('static renderToString', () => {
- const style1 = { alignItems: 'center', opacity: 1 }
- const style2 = { alignItems: 'center', opacity: 1 }
- StyleSheetRegistry.registerStyle(style1)
- StyleSheetRegistry.registerStyle(style2)
-
- const actual = StyleSheetRegistry.renderToString()
- const expected = `/* 2 unique declarations */
-.__style1{-ms-flex-align:center;-webkit-align-items:center;-webkit-box-align:center;align-items:center;}
-.__style2{opacity:1;}`
-
- assert.equal(actual, expected)
- })
-
- test('static getStyleAsNativeProps', () => {
- const style = { borderColorTop: 'white', opacity: 1 }
- const style1 = { opacity: 1 }
- StyleSheetRegistry.registerStyle(style1)
-
- // canUseCSS = false
- const actual1 = StyleSheetRegistry.getStyleAsNativeProps(style)
- const expected1 = {
- className: '',
- style: { borderColorTop: 'white', opacity: 1 }
- }
- assert.deepEqual(actual1, expected1)
-
- // canUseCSS = true
- const actual2 = StyleSheetRegistry.getStyleAsNativeProps(style, true)
- const expected2 = {
- className: '__style1',
- style: { borderColorTop: 'white' }
- }
- assert.deepEqual(actual2, expected2)
- })
-})
diff --git a/src/apis/StyleSheet/__tests__/createReactStyleObject-test.js b/src/apis/StyleSheet/__tests__/createReactStyleObject-test.js
new file mode 100644
index 00000000..f876432b
--- /dev/null
+++ b/src/apis/StyleSheet/__tests__/createReactStyleObject-test.js
@@ -0,0 +1,13 @@
+/* eslint-env mocha */
+
+import assert from 'assert'
+import createReactStyleObject from '../createReactStyleObject'
+
+suite('apis/StyleSheet/createReactStyleObject', () => {
+ test('converts ReactNative style to ReactDOM style', () => {
+ const reactNativeStyle = { display: 'flex', marginVertical: 0, opacity: 0 }
+ const expectedStyle = { display: 'flex', marginTop: '0px', marginBottom: '0px', opacity: 0 }
+
+ assert.deepEqual(createReactStyleObject(reactNativeStyle), expectedStyle)
+ })
+})
diff --git a/src/apis/StyleSheet/__tests__/hyphenate-test.js b/src/apis/StyleSheet/__tests__/hyphenate-test.js
deleted file mode 100644
index ca2f1e31..00000000
--- a/src/apis/StyleSheet/__tests__/hyphenate-test.js
+++ /dev/null
@@ -1,16 +0,0 @@
-/* eslint-env mocha */
-
-import assert from 'assert'
-import hyphenate from '../hyphenate'
-
-suite('apis/StyleSheet/hyphenate', () => {
- test('style property', () => {
- assert.equal(hyphenate('alignItems'), 'align-items')
- assert.equal(hyphenate('color'), 'color')
- })
- test('vendor prefixed style property', () => {
- assert.equal(hyphenate('MozTransition'), '-moz-transition')
- assert.equal(hyphenate('msTransition'), '-ms-transition')
- assert.equal(hyphenate('WebkitTransition'), '-webkit-transition')
- })
-})
diff --git a/src/apis/StyleSheet/__tests__/index-test.js b/src/apis/StyleSheet/__tests__/index-test.js
index 9c67d128..f3601cf5 100644
--- a/src/apis/StyleSheet/__tests__/index-test.js
+++ b/src/apis/StyleSheet/__tests__/index-test.js
@@ -1,60 +1,70 @@
/* eslint-env mocha */
-import { resetCSS, predefinedCSS } from '../predefs'
import assert from 'assert'
+import { defaultStyles } from '../predefs'
+import isPlainObject from 'lodash/isPlainObject'
import StyleSheet from '..'
-const styles = { root: { opacity: 1 } }
-
suite('apis/StyleSheet', () => {
setup(() => {
- StyleSheet._destroy()
+ StyleSheet._reset()
+ })
+
+ test('absoluteFill', () => {
+ assert(Number.isInteger(StyleSheet.absoluteFill) === true)
+ })
+
+ test('absoluteFillObject', () => {
+ assert.ok(isPlainObject(StyleSheet.absoluteFillObject) === true)
})
suite('create', () => {
- test('returns styles object', () => {
- assert.equal(StyleSheet.create(styles), styles)
+ test('replaces styles with numbers', () => {
+ const style = StyleSheet.create({ root: { opacity: 1 } })
+ assert(Number.isInteger(style.root) === true)
})
- test('updates already-rendered style sheet', () => {
- // setup
- const div = document.createElement('div')
- document.body.appendChild(div)
- StyleSheet.create(styles)
- div.innerHTML = ``
-
- // test
+ test('renders a style sheet in the browser', () => {
StyleSheet.create({ root: { color: 'red' } })
assert.equal(
- document.getElementById(StyleSheet.elementId).textContent,
- `${resetCSS}\n${predefinedCSS}\n` +
- `/* 2 unique declarations */\n` +
- `.__style1{opacity:1;}\n` +
- '.__style2{color:red;}'
+ document.getElementById('__react-native-style').textContent,
+ defaultStyles
)
-
- // teardown
- document.body.removeChild(div)
})
})
- test('renderToString', () => {
- StyleSheet.create(styles)
+ test('flatten', () => {
+ assert(typeof StyleSheet.flatten === 'function')
+ })
+ test('hairlineWidth', () => {
+ assert(Number.isInteger(StyleSheet.hairlineWidth) === true)
+ })
+
+ test('render', () => {
assert.equal(
- StyleSheet.renderToString(),
- `${resetCSS}\n${predefinedCSS}\n` +
- `/* 1 unique declarations */\n` +
- '.__style1{opacity:1;}'
+ StyleSheet.render().props.dangerouslySetInnerHTML.__html,
+ defaultStyles
)
})
test('resolve', () => {
assert.deepEqual(
- StyleSheet.resolve({ className: 'test', style: styles.root }),
- {
+ StyleSheet.resolve({
className: 'test',
- style: { opacity: 1 }
+ style: {
+ display: 'flex',
+ opacity: 1,
+ pointerEvents: 'box-none'
+ }
+ }),
+ {
+ className: 'test __style_df __style_pebn',
+ style: {
+ display: 'flex',
+ opacity: 1,
+ pointerEvents: 'box-none'
+ }
}
)
})
diff --git a/src/apis/StyleSheet/__tests__/normalizeValue-test.js b/src/apis/StyleSheet/__tests__/normalizeValue-test.js
index 9eb6a489..42d91cf2 100644
--- a/src/apis/StyleSheet/__tests__/normalizeValue-test.js
+++ b/src/apis/StyleSheet/__tests__/normalizeValue-test.js
@@ -9,5 +9,6 @@ suite('apis/StyleSheet/normalizeValue', () => {
})
test('ignores unitless property values', () => {
assert.deepEqual(normalizeValue('flexGrow', 1), 1)
+ assert.deepEqual(normalizeValue('scale', 2), 2)
})
})
diff --git a/src/apis/StyleSheet/createReactStyleObject.js b/src/apis/StyleSheet/createReactStyleObject.js
index 4e4e04dd..26a52dd5 100644
--- a/src/apis/StyleSheet/createReactStyleObject.js
+++ b/src/apis/StyleSheet/createReactStyleObject.js
@@ -1,7 +1,22 @@
import expandStyle from './expandStyle'
-import flattenStyle from '../StyleSheet/flattenStyle'
-import processTransform from '../StyleSheet/processTransform'
+import flattenStyle from '../../modules/flattenStyle'
+import prefixAll from 'inline-style-prefixer/static'
+import processTransform from './processTransform'
-const createReactStyleObject = (style) => processTransform(expandStyle(flattenStyle(style)))
+const addVendorPrefixes = (style) => {
+ let prefixedStyles = prefixAll(style)
+ // React@15 removed undocumented support for fallback values in
+ // inline-styles. Revert array values to the standard CSS value
+ for (const prop in prefixedStyles) {
+ const value = prefixedStyles[prop]
+ if (Array.isArray(value)) {
+ prefixedStyles[prop] = value[value.length - 1]
+ }
+ }
+ return prefixedStyles
+}
-module.exports = createReactStyleObject
+const _createReactDOMStyleObject = (reactNativeStyle) => processTransform(expandStyle(flattenStyle(reactNativeStyle)))
+const createReactDOMStyleObject = (reactNativeStyle) => addVendorPrefixes(_createReactDOMStyleObject(reactNativeStyle))
+
+module.exports = createReactDOMStyleObject
diff --git a/src/apis/StyleSheet/expandStyle.js b/src/apis/StyleSheet/expandStyle.js
index 0b9e454a..f382d252 100644
--- a/src/apis/StyleSheet/expandStyle.js
+++ b/src/apis/StyleSheet/expandStyle.js
@@ -11,6 +11,7 @@
import normalizeValue from './normalizeValue'
+const emptyObject = {}
const styleShortFormProperties = {
borderColor: [ 'borderTopColor', 'borderRightColor', 'borderBottomColor', 'borderLeftColor' ],
borderRadius: [ 'borderTopLeftRadius', 'borderTopRightRadius', 'borderBottomRightRadius', 'borderBottomLeftRadius' ],
@@ -43,8 +44,8 @@ const createStyleReducer = (originalStyle) => {
// React Native treats `flex:1` like `flex:1 1 auto`
if (prop === 'flex') {
style.flexGrow = value
- if (style.flexShrink == null) { style.flexShrink = 1 }
- if (style.flexBasis == null) { style.flexBasis = 'auto' }
+ style.flexShrink = 1
+ style.flexBasis = 'auto'
// React Native accepts 'center' as a value
} else if (prop === 'textAlignVertical') {
style.verticalAlign = (value === 'center' ? 'middle' : value)
@@ -63,7 +64,7 @@ const createStyleReducer = (originalStyle) => {
}
}
-const expandStyle = (style) => {
+const expandStyle = (style = emptyObject) => {
const sortedStyleProps = alphaSort(Object.keys(style))
const styleReducer = createStyleReducer(style)
return sortedStyleProps.reduce(styleReducer, {})
diff --git a/src/apis/StyleSheet/flattenStyle.js b/src/apis/StyleSheet/flattenStyle.js
deleted file mode 100644
index 4794b6ac..00000000
--- a/src/apis/StyleSheet/flattenStyle.js
+++ /dev/null
@@ -1,31 +0,0 @@
-/**
- * Copyright (c) 2016-present, Nicolas Gallagher.
- * Copyright (c) 2015-present, Facebook, Inc.
- * All rights reserved.
- *
- * @flow
- */
-import invariant from 'fbjs/lib/invariant'
-
-module.exports = function flattenStyle(style): ?Object {
- if (!style) {
- return undefined
- }
-
- invariant(style !== true, 'style may be false but not true')
-
- if (!Array.isArray(style)) {
- return style
- }
-
- const result = {}
- for (let i = 0; i < style.length; ++i) {
- const computedStyle = flattenStyle(style[i])
- if (computedStyle) {
- for (const key in computedStyle) {
- result[key] = computedStyle[key]
- }
- }
- }
- return result
-}
diff --git a/src/apis/StyleSheet/hyphenate.js b/src/apis/StyleSheet/hyphenate.js
deleted file mode 100644
index e68b8266..00000000
--- a/src/apis/StyleSheet/hyphenate.js
+++ /dev/null
@@ -1 +0,0 @@
-module.exports = (string) => (string.replace(/([A-Z])/g, '-$1').toLowerCase()).replace(/^ms-/, '-ms-')
diff --git a/src/apis/StyleSheet/index.js b/src/apis/StyleSheet/index.js
index a8db0945..9f81e6a9 100644
--- a/src/apis/StyleSheet/index.js
+++ b/src/apis/StyleSheet/index.js
@@ -1,75 +1,76 @@
-import { resetCSS, predefinedCSS } from './predefs'
-import flattenStyle from './flattenStyle'
-import StyleSheetRegistry from './StyleSheetRegistry'
+import createReactStyleObject from './createReactStyleObject'
+import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment'
+import flattenStyle from '../../modules/flattenStyle'
+import React from 'react'
+import ReactNativePropRegistry from '../../modules/ReactNativePropRegistry'
import StyleSheetValidation from './StyleSheetValidation'
+import { defaultStyles, mapStyleToClassName } from './predefs'
-const ELEMENT_ID = 'react-stylesheet'
let isRendered = false
-let lastStyleSheet = ''
+let styleElement
+const STYLE_SHEET_ID = '__react-native-style'
-/**
- * Initialize the store with pointer-event styles mapping to our custom pointer
- * event classes
- */
-
-/**
- * Destroy existing styles
- */
-const _destroy = () => {
- isRendered = false
- StyleSheetRegistry._reset()
+const _injectStyleSheet = () => {
+ // check if the server rendered the style sheet
+ styleElement = document.getElementById(STYLE_SHEET_ID)
+ // if not, inject the style sheet
+ if (!styleElement) { document.head.insertAdjacentHTML('afterbegin', renderToString()) }
+ isRendered = true
}
+const _reset = () => {
+ if (styleElement) { document.head.removeChild(styleElement) }
+ styleElement = null
+ isRendered = false
+}
+
+const absoluteFillObject = { position: 'absolute', left: 0, right: 0, top: 0, bottom: 0 }
+const absoluteFill = ReactNativePropRegistry.register(absoluteFillObject)
+
const create = (styles: Object): Object => {
- for (const key in styles) {
- StyleSheetValidation.validateStyle(key, styles)
- StyleSheetRegistry.registerStyle(styles[key])
+ if (!isRendered && ExecutionEnvironment.canUseDOM) {
+ _injectStyleSheet()
}
- // update the style sheet in place
- if (isRendered) {
- const stylesheet = document.getElementById(ELEMENT_ID)
- if (stylesheet) {
- const newStyleSheet = renderToString()
- if (lastStyleSheet !== newStyleSheet) {
- stylesheet.textContent = newStyleSheet
- lastStyleSheet = newStyleSheet
- }
- } else if (process.env.NODE_ENV !== 'production') {
- console.error(`ReactNative: cannot find "${ELEMENT_ID}" element`)
+ const result = {}
+ for (let key in styles) {
+ StyleSheetValidation.validateStyle(key, styles)
+ result[key] = ReactNativePropRegistry.register(styles[key])
+ }
+ return result
+}
+
+const render = () =>
+
+const renderToString = () => ``
+
+/**
+ * Accepts React props and converts style declarations to classNames when necessary
+ */
+const resolve = (props) => {
+ let className = props.className || ''
+ let style = createReactStyleObject(props.style)
+ for (const prop in style) {
+ const value = style[prop]
+ const replacementClassName = mapStyleToClassName(prop, value)
+ if (replacementClassName) {
+ className += ` ${replacementClassName}`
+ // delete style[prop]
}
}
- return styles
-}
-
-/**
- * Render the styles as a CSS style sheet
- */
-const renderToString = () => {
- const css = StyleSheetRegistry.renderToString()
- isRendered = true
- return `${resetCSS}\n${predefinedCSS}\n${css}`
-}
-
-/**
- * Accepts React props and converts inline styles to single purpose classes
- * where possible.
- */
-const resolve = ({ className, style = {} }) => {
- const props = StyleSheetRegistry.getStyleAsNativeProps(style, isRendered)
- return {
- ...props,
- className: className ? `${props.className} ${className}`.trim() : props.className
- }
+ return { className, style }
}
module.exports = {
- _destroy,
+ _reset,
+ absoluteFill,
+ absoluteFillObject,
create,
- elementId: ELEMENT_ID,
hairlineWidth: 1,
flatten: flattenStyle,
- renderToString,
+ /* @platform web */
+ render,
+ /* @platform web */
resolve
}
diff --git a/src/apis/StyleSheet/predefs.js b/src/apis/StyleSheet/predefs.js
index 9506c54a..4fa67ddf 100644
--- a/src/apis/StyleSheet/predefs.js
+++ b/src/apis/StyleSheet/predefs.js
@@ -1,24 +1,38 @@
-/**
- * Reset unwanted styles beyond the control of React inline styles
- */
-export const resetCSS =
-`/* React Native for Web */
-html {font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}
-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 {display:none}`
+const DISPLAY_FLEX_CLASSNAME = '__style_df'
+const POINTER_EVENTS_AUTO_CLASSNAME = '__style_pea'
+const POINTER_EVENTS_BOX_NONE_CLASSNAME = '__style_pebn'
+const POINTER_EVENTS_BOX_ONLY_CLASSNAME = '__style_pebo'
+const POINTER_EVENTS_NONE_CLASSNAME = '__style_pen'
-/**
- * Custom pointer event styles
- */
-export const predefinedCSS =
-`/* pointer-events */
-.__style_pea, .__style_pebo, .__style_pebn * {pointer-events:auto}
-.__style_pen, .__style_pebo *, .__style_pebn {pointer-events:none}`
-
-export const predefinedClassNames = {
- 'pointerEvents:auto': '__style_pea',
- 'pointerEvents:box-none': '__style_pebn',
- 'pointerEvents:box-only': '__style_pebo',
- 'pointerEvents:none': '__style_pen'
+const styleAsClassName = {
+ display: {
+ 'flex': DISPLAY_FLEX_CLASSNAME
+ },
+ pointerEvents: {
+ 'auto': POINTER_EVENTS_AUTO_CLASSNAME,
+ 'box-none': POINTER_EVENTS_BOX_NONE_CLASSNAME,
+ 'box-only': POINTER_EVENTS_BOX_ONLY_CLASSNAME,
+ 'none': POINTER_EVENTS_NONE_CLASSNAME
+ }
}
+
+export const mapStyleToClassName = (prop, value) => {
+ return styleAsClassName[prop] && styleAsClassName[prop][value]
+}
+
+// reset unwanted styles beyond the control of React inline styles
+const resetCSS =
+'/* React Native */\n' +
+'html {font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}\n' +
+'body {margin:0}\n' +
+'button::-moz-focus-inner, input::-moz-focus-inner {border:0;padding:0}\n' +
+'input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration {display:none}'
+
+const helperCSS =
+// vendor prefix 'display:flex' until React supports fallback values for inline styles
+`.${DISPLAY_FLEX_CLASSNAME} {display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex}\n` +
+// implement React Native's pointer event values
+`.${POINTER_EVENTS_AUTO_CLASSNAME}, .${POINTER_EVENTS_BOX_ONLY_CLASSNAME}, .${POINTER_EVENTS_BOX_NONE_CLASSNAME} * {pointer-events:auto}\n` +
+`.${POINTER_EVENTS_NONE_CLASSNAME}, .${POINTER_EVENTS_BOX_ONLY_CLASSNAME} *, .${POINTER_EVENTS_NONE_CLASSNAME} {pointer-events:none}`
+
+export const defaultStyles = `${resetCSS}\n${helperCSS}`
diff --git a/src/apis/UIManager/__tests__/index-test.js b/src/apis/UIManager/__tests__/index-test.js
index 47d8d971..1bd2ac48 100644
--- a/src/apis/UIManager/__tests__/index-test.js
+++ b/src/apis/UIManager/__tests__/index-test.js
@@ -109,7 +109,7 @@ suite('apis/UIManager', () => {
assert.equal(node.getAttribute('class'), 'existing extra')
})
- test('adds new style to existing style', () => {
+ test('adds correct DOM styles to existing style', () => {
const node = createNode({ color: 'red' })
const props = { style: { marginVertical: 0, opacity: 0 } }
UIManager.updateView(node, props, componentStub)
diff --git a/src/apis/UIManager/index.js b/src/apis/UIManager/index.js
index 1e5d1013..5f07e4bf 100644
--- a/src/apis/UIManager/index.js
+++ b/src/apis/UIManager/index.js
@@ -35,7 +35,6 @@ const UIManager = {
updateView(node, props, component /* only needed to surpress React errors in __DEV__ */) {
for (const prop in props) {
- let nativeProp
const value = props[prop]
switch (prop) {
@@ -48,12 +47,13 @@ const UIManager = {
)
break
case 'class':
- case 'className':
- nativeProp = 'class'
+ case 'className': {
+ const nativeProp = 'class'
// prevent class names managed by React Native from being replaced
const className = node.getAttribute(nativeProp) + ' ' + value
node.setAttribute(nativeProp, className)
break
+ }
case 'text':
case 'value':
// native platforms use `text` prop to replace text input value
diff --git a/src/components/TextInput/index.js b/src/components/TextInput/index.js
index e0968420..10fcefbe 100644
--- a/src/components/TextInput/index.js
+++ b/src/components/TextInput/index.js
@@ -135,7 +135,7 @@ class TextInput extends Component {
onFocus: this._handleFocus,
onSelect: onSelectionChange && this._handleSelectionChange,
readOnly: !editable,
- style: { ...styles.input, ...textStyles, outline: style.outline },
+ style: [ styles.input, textStyles, { outline: style.outline } ],
value
}
diff --git a/src/components/View/__tests__/index-test.js b/src/components/View/__tests__/index-test.js
index a7e2f9ed..37a44465 100644
--- a/src/components/View/__tests__/index-test.js
+++ b/src/components/View/__tests__/index-test.js
@@ -1,6 +1,7 @@
/* eslint-env mocha */
import assert from 'assert'
+import includes from 'lodash/includes'
import React from 'react'
import { shallow } from 'enzyme'
import View from '../'
@@ -14,7 +15,7 @@ suite('components/View', () => {
test('prop "pointerEvents"', () => {
const view = shallow()
- assert.equal(view.prop('className'), '__style_pebo')
+ assert.ok(includes(view.prop('className'), '__style_pebo') === true)
})
test('prop "style"', () => {
diff --git a/src/modules/ReactNativePropRegistry/index.js b/src/modules/ReactNativePropRegistry/index.js
new file mode 100644
index 00000000..b60ba5f0
--- /dev/null
+++ b/src/modules/ReactNativePropRegistry/index.js
@@ -0,0 +1,45 @@
+/* eslint-disable */
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ * @providesModule ReactNativePropRegistry
+ * @flow
+ */
+'use strict';
+
+const emptyObject = {};
+const objects = {};
+let uniqueID = 1;
+
+class ReactNativePropRegistry {
+ static register(object: Object): number {
+ let id = ++uniqueID;
+ if (process.env.NODE_ENV !== 'production') {
+ Object.freeze(object);
+ }
+ objects[id] = object;
+ return id;
+ }
+
+ static getByID(id: number): Object {
+ if (!id) {
+ // Used in the style={[condition && id]} pattern,
+ // we want it to be a no-op when the value is false or null
+ return emptyObject;
+ }
+
+ const object = objects[id];
+ if (!object) {
+ console.warn('Invalid style with id `' + id + '`. Skipping ...');
+ return emptyObject;
+ }
+ return object;
+ }
+}
+
+module.exports = ReactNativePropRegistry;
diff --git a/src/apis/StyleSheet/__tests__/flattenStyle-test.js b/src/modules/flattenStyle/__tests__/index-test.js
similarity index 94%
rename from src/apis/StyleSheet/__tests__/flattenStyle-test.js
rename to src/modules/flattenStyle/__tests__/index-test.js
index 5d5576d6..830d5f3b 100644
--- a/src/apis/StyleSheet/__tests__/flattenStyle-test.js
+++ b/src/modules/flattenStyle/__tests__/index-test.js
@@ -10,9 +10,9 @@
*/
import assert from 'assert'
-import flattenStyle from '../flattenStyle'
+import flattenStyle from '..'
-suite('apis/StyleSheet/flattenStyle', () => {
+suite('modules/flattenStyle', () => {
test('should merge style objects', () => {
const style1 = {opacity: 1}
const style2 = {order: 2}
diff --git a/src/modules/flattenStyle/index.js b/src/modules/flattenStyle/index.js
new file mode 100644
index 00000000..7b9265c4
--- /dev/null
+++ b/src/modules/flattenStyle/index.js
@@ -0,0 +1,47 @@
+/* eslint-disable */
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ * @providesModule flattenStyle
+ * @flow
+ */
+'use strict';
+
+var ReactNativePropRegistry = require('../ReactNativePropRegistry');
+var invariant = require('fbjs/lib/invariant');
+
+function getStyle(style) {
+ if (typeof style === 'number') {
+ return ReactNativePropRegistry.getByID(style);
+ }
+ return style;
+}
+
+function flattenStyle(style: ?StyleObj): ?Object {
+ if (!style) {
+ return undefined;
+ }
+ invariant(style !== true, 'style may be false but not true');
+
+ if (!Array.isArray(style)) {
+ return getStyle(style);
+ }
+
+ var result = {};
+ for (var i = 0, styleLength = style.length; i < styleLength; ++i) {
+ var computedStyle = flattenStyle(style[i]);
+ if (computedStyle) {
+ for (var key in computedStyle) {
+ result[key] = computedStyle[key];
+ }
+ }
+ }
+ return result;
+}
+
+module.exports = flattenStyle;