diff --git a/package.json b/package.json index fbe9a030..9bf5dd18 100644 --- a/package.json +++ b/package.json @@ -11,9 +11,9 @@ "build:examples": "build-storybook -o dist-examples -c ./examples/.storybook", "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 -", + "examples": "start-storybook -p 9001 -c ./examples/.storybook", "lint": "eslint src", "prepublish": "npm run build && npm run build:umd", - "storybook": "start-storybook -p 9001 -c ./examples/.storybook", "test": "karma start karma.config.js", "test:watch": "npm run test -- --no-single-run" }, diff --git a/src/apis/StyleSheet/StyleSheetValidation.js b/src/apis/StyleSheet/StyleSheetValidation.js index efe0b588..307ad574 100644 --- a/src/apis/StyleSheet/StyleSheetValidation.js +++ b/src/apis/StyleSheet/StyleSheetValidation.js @@ -12,11 +12,13 @@ import TextStylePropTypes from '../../components/Text/TextStylePropTypes' import ViewStylePropTypes from '../../components/View/ViewStylePropTypes' import warning from 'fbjs/lib/warning' +const allStylePropTypes = {} + class StyleSheetValidation { static validateStyleProp(prop, style, caller) { if (process.env.NODE_ENV !== 'production') { 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, ' ') styleError(message1, style, caller, message2) } else { @@ -51,8 +53,6 @@ const styleError = (message1, style, caller, message2) => { ) } -const allStylePropTypes = {} - StyleSheetValidation.addValidStylePropTypes(ImageStylePropTypes) StyleSheetValidation.addValidStylePropTypes(TextStylePropTypes) StyleSheetValidation.addValidStylePropTypes(ViewStylePropTypes) diff --git a/src/apis/StyleSheet/__tests__/index-test.js b/src/apis/StyleSheet/__tests__/index-test.js index f3601cf5..b72e6419 100644 --- a/src/apis/StyleSheet/__tests__/index-test.js +++ b/src/apis/StyleSheet/__tests__/index-test.js @@ -1,7 +1,7 @@ /* eslint-env mocha */ import assert from 'assert' -import { defaultStyles } from '../predefs' +import { getDefaultStyleSheet } from '../css' import isPlainObject from 'lodash/isPlainObject' import StyleSheet from '..' @@ -28,7 +28,7 @@ suite('apis/StyleSheet', () => { StyleSheet.create({ root: { color: 'red' } }) assert.equal( document.getElementById('__react-native-style').textContent, - defaultStyles + getDefaultStyleSheet() ) }) }) @@ -44,7 +44,7 @@ suite('apis/StyleSheet', () => { test('render', () => { assert.equal( StyleSheet.render().props.dangerouslySetInnerHTML.__html, - defaultStyles + getDefaultStyleSheet() ) }) @@ -61,9 +61,9 @@ suite('apis/StyleSheet', () => { { className: 'test __style_df __style_pebn', style: { - display: 'flex', + display: null, opacity: 1, - pointerEvents: 'box-none' + pointerEvents: null } } ) diff --git a/src/apis/StyleSheet/predefs.js b/src/apis/StyleSheet/css.js similarity index 87% rename from src/apis/StyleSheet/predefs.js rename to src/apis/StyleSheet/css.js index 4fa67ddf..635d2a1e 100644 --- a/src/apis/StyleSheet/predefs.js +++ b/src/apis/StyleSheet/css.js @@ -4,6 +4,21 @@ const POINTER_EVENTS_BOX_NONE_CLASSNAME = '__style_pebn' const POINTER_EVENTS_BOX_ONLY_CLASSNAME = '__style_pebo' 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 = { display: { '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] } - -// 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/StyleSheet/index.js b/src/apis/StyleSheet/index.js index 9f81e6a9..1cfffc43 100644 --- a/src/apis/StyleSheet/index.js +++ b/src/apis/StyleSheet/index.js @@ -1,76 +1,88 @@ +import * as css from './css' 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' -let isRendered = false let styleElement +let shouldInsertStyleSheet = ExecutionEnvironment.canUseDOM + 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 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 => { - if (!isRendered && ExecutionEnvironment.canUseDOM) { - _injectStyleSheet() + if (!styleElement) { + document.head.insertAdjacentHTML( + 'afterbegin', + `` + ) + shouldInsertStyleSheet = false } - - const result = {} - for (let key in styles) { - StyleSheetValidation.validateStyle(key, styles) - result[key] = ReactNativePropRegistry.register(styles[key]) - } - return result -} - -const render = () => ` - -/** - * 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 = { - _reset, - absoluteFill, + /** + * For testing + * @private + */ + _reset() { + if (styleElement) { + document.head.removeChild(styleElement) + styleElement = null + shouldInsertStyleSheet = true + } + }, + + absoluteFill: ReactNativePropRegistry.register(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, + flatten: flattenStyle, + /* @platform web */ - render, - /* @platform web */ - resolve + render() { + return