mirror of
https://github.com/zoriya/react-native-web.git
synced 2026-06-01 10:07:35 +00:00
Minor refactor of StyleSheet helpers
This commit is contained in:
+1
-1
@@ -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"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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}`
|
|
||||||
@@ -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 }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user