Minor refactor of StyleSheet helpers

This commit is contained in:
Nicolas Gallagher
2016-07-23 18:54:41 -07:00
parent 6f75fb3e0d
commit 1be2c810d1
5 changed files with 93 additions and 81 deletions
+1 -1
View File
@@ -11,9 +11,9 @@
"build:examples": "build-storybook -o dist-examples -c ./examples/.storybook", "build:examples": "build-storybook -o dist-examples -c ./examples/.storybook",
"build:umd": "webpack --config webpack.config.js --sort-assets-by --progress", "build:umd": "webpack --config webpack.config.js --sort-assets-by --progress",
"deploy:examples": "git checkout gh-pages && rm -rf ./storybook && mv dist-examples storybook && git add -A && git commit -m \"Storybook deploy\" && git push origin gh-pages && git checkout -", "deploy:examples": "git checkout gh-pages && rm -rf ./storybook && mv dist-examples storybook && git add -A && git commit -m \"Storybook deploy\" && git push origin gh-pages && git checkout -",
"examples": "start-storybook -p 9001 -c ./examples/.storybook",
"lint": "eslint src", "lint": "eslint src",
"prepublish": "npm run build && npm run build:umd", "prepublish": "npm run build && npm run build:umd",
"storybook": "start-storybook -p 9001 -c ./examples/.storybook",
"test": "karma start karma.config.js", "test": "karma start karma.config.js",
"test:watch": "npm run test -- --no-single-run" "test:watch": "npm run test -- --no-single-run"
}, },
+3 -3
View File
@@ -12,11 +12,13 @@ import TextStylePropTypes from '../../components/Text/TextStylePropTypes'
import ViewStylePropTypes from '../../components/View/ViewStylePropTypes' import ViewStylePropTypes from '../../components/View/ViewStylePropTypes'
import warning from 'fbjs/lib/warning' import warning from 'fbjs/lib/warning'
const allStylePropTypes = {}
class StyleSheetValidation { class StyleSheetValidation {
static validateStyleProp(prop, style, caller) { static validateStyleProp(prop, style, caller) {
if (process.env.NODE_ENV !== 'production') { if (process.env.NODE_ENV !== 'production') {
if (allStylePropTypes[prop] === undefined) { if (allStylePropTypes[prop] === undefined) {
const message1 = `"${prop}" is not a valid style property.` const message1 = `"${prop}" is not a valid style property on Web.`
const message2 = '\nValid style props: ' + JSON.stringify(Object.keys(allStylePropTypes).sort(), null, ' ') const message2 = '\nValid style props: ' + JSON.stringify(Object.keys(allStylePropTypes).sort(), null, ' ')
styleError(message1, style, caller, message2) styleError(message1, style, caller, message2)
} else { } else {
@@ -51,8 +53,6 @@ const styleError = (message1, style, caller, message2) => {
) )
} }
const allStylePropTypes = {}
StyleSheetValidation.addValidStylePropTypes(ImageStylePropTypes) StyleSheetValidation.addValidStylePropTypes(ImageStylePropTypes)
StyleSheetValidation.addValidStylePropTypes(TextStylePropTypes) StyleSheetValidation.addValidStylePropTypes(TextStylePropTypes)
StyleSheetValidation.addValidStylePropTypes(ViewStylePropTypes) StyleSheetValidation.addValidStylePropTypes(ViewStylePropTypes)
+5 -5
View File
@@ -1,7 +1,7 @@
/* eslint-env mocha */ /* eslint-env mocha */
import assert from 'assert' import assert from 'assert'
import { defaultStyles } from '../predefs' import { getDefaultStyleSheet } from '../css'
import isPlainObject from 'lodash/isPlainObject' import isPlainObject from 'lodash/isPlainObject'
import StyleSheet from '..' import StyleSheet from '..'
@@ -28,7 +28,7 @@ suite('apis/StyleSheet', () => {
StyleSheet.create({ root: { color: 'red' } }) StyleSheet.create({ root: { color: 'red' } })
assert.equal( assert.equal(
document.getElementById('__react-native-style').textContent, document.getElementById('__react-native-style').textContent,
defaultStyles getDefaultStyleSheet()
) )
}) })
}) })
@@ -44,7 +44,7 @@ suite('apis/StyleSheet', () => {
test('render', () => { test('render', () => {
assert.equal( assert.equal(
StyleSheet.render().props.dangerouslySetInnerHTML.__html, StyleSheet.render().props.dangerouslySetInnerHTML.__html,
defaultStyles getDefaultStyleSheet()
) )
}) })
@@ -61,9 +61,9 @@ suite('apis/StyleSheet', () => {
{ {
className: 'test __style_df __style_pebn', className: 'test __style_df __style_pebn',
style: { style: {
display: 'flex', display: null,
opacity: 1, opacity: 1,
pointerEvents: 'box-none' pointerEvents: null
} }
} }
) )
@@ -4,6 +4,21 @@ const POINTER_EVENTS_BOX_NONE_CLASSNAME = '__style_pebn'
const POINTER_EVENTS_BOX_ONLY_CLASSNAME = '__style_pebo' const POINTER_EVENTS_BOX_ONLY_CLASSNAME = '__style_pebo'
const POINTER_EVENTS_NONE_CLASSNAME = '__style_pen' const POINTER_EVENTS_NONE_CLASSNAME = '__style_pen'
const CSS_RESET =
// reset unwanted styles
'/* 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 CSS_HELPERS =
// 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}`
const styleAsClassName = { const styleAsClassName = {
display: { display: {
'flex': DISPLAY_FLEX_CLASSNAME 'flex': DISPLAY_FLEX_CLASSNAME
@@ -16,23 +31,8 @@ const styleAsClassName = {
} }
} }
export const mapStyleToClassName = (prop, value) => { export const getDefaultStyleSheet = () => `${CSS_RESET}\n${CSS_HELPERS}`
export const getStyleAsHelperClassName = (prop, value) => {
return styleAsClassName[prop] && styleAsClassName[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}`
+66 -54
View File
@@ -1,76 +1,88 @@
import * as css from './css'
import createReactStyleObject from './createReactStyleObject' import createReactStyleObject from './createReactStyleObject'
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment' import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment'
import flattenStyle from '../../modules/flattenStyle' import flattenStyle from '../../modules/flattenStyle'
import React from 'react' import React from 'react'
import ReactNativePropRegistry from '../../modules/ReactNativePropRegistry' import ReactNativePropRegistry from '../../modules/ReactNativePropRegistry'
import StyleSheetValidation from './StyleSheetValidation' import StyleSheetValidation from './StyleSheetValidation'
import { defaultStyles, mapStyleToClassName } from './predefs'
let isRendered = false
let styleElement let styleElement
let shouldInsertStyleSheet = ExecutionEnvironment.canUseDOM
const STYLE_SHEET_ID = '__react-native-style' const STYLE_SHEET_ID = '__react-native-style'
const _injectStyleSheet = () => { const absoluteFillObject = { position: 'absolute', left: 0, right: 0, top: 0, bottom: 0 }
const defaultStyleSheet = css.getDefaultStyleSheet()
const insertStyleSheet = () => {
// check if the server rendered the style sheet // check if the server rendered the style sheet
styleElement = document.getElementById(STYLE_SHEET_ID) styleElement = document.getElementById(STYLE_SHEET_ID)
// if not, inject the style sheet // if not, inject the style sheet
if (!styleElement) { document.head.insertAdjacentHTML('afterbegin', renderToString()) } if (!styleElement) {
isRendered = true document.head.insertAdjacentHTML(
} 'afterbegin',
`<style id="${STYLE_SHEET_ID}">${defaultStyleSheet}</style>`
const _reset = () => { )
if (styleElement) { document.head.removeChild(styleElement) } shouldInsertStyleSheet = false
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 => {
if (!isRendered && ExecutionEnvironment.canUseDOM) {
_injectStyleSheet()
} }
const result = {}
for (let key in styles) {
StyleSheetValidation.validateStyle(key, styles)
result[key] = ReactNativePropRegistry.register(styles[key])
}
return result
}
const render = () => <style dangerouslySetInnerHTML={{ __html: defaultStyles }} id={STYLE_SHEET_ID} />
const renderToString = () => `<style id="${STYLE_SHEET_ID}">${defaultStyles}</style>`
/**
* 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 { className, style }
} }
module.exports = { module.exports = {
_reset, /**
absoluteFill, * For testing
* @private
*/
_reset() {
if (styleElement) {
document.head.removeChild(styleElement)
styleElement = null
shouldInsertStyleSheet = true
}
},
absoluteFill: ReactNativePropRegistry.register(absoluteFillObject),
absoluteFillObject, absoluteFillObject,
create,
create(styles) {
if (shouldInsertStyleSheet) {
insertStyleSheet()
}
const result = {}
for (const key in styles) {
StyleSheetValidation.validateStyle(key, styles)
result[key] = ReactNativePropRegistry.register(styles[key])
}
return result
},
hairlineWidth: 1, hairlineWidth: 1,
flatten: flattenStyle, flatten: flattenStyle,
/* @platform web */ /* @platform web */
render, render() {
/* @platform web */ return <style dangerouslySetInnerHTML={{ __html: defaultStyleSheet }} id={STYLE_SHEET_ID} />
resolve },
/**
* Accepts React props and converts style declarations to classNames when necessary
* @platform web
*/
resolve(props) {
let className = props.className || ''
let style = createReactStyleObject(props.style)
for (const prop in style) {
const value = style[prop]
const replacementClassName = css.getStyleAsHelperClassName(prop, value)
if (replacementClassName) {
className += ` ${replacementClassName}`
style[prop] = null
}
}
return { className, style }
}
} }