diff --git a/examples/components/App.js b/examples/components/App.js index 858154d0..09aa7988 100644 --- a/examples/components/App.js +++ b/examples/components/App.js @@ -213,7 +213,8 @@ export default class App extends React.Component { const styles = StyleSheet.create({ root: { common: { - margin: '0 auto' + marginVertical: 0, + marginHorizontal: 'auto' }, mqSmall: { maxWidth: '400px' @@ -230,7 +231,7 @@ const styles = StyleSheet.create({ alignItems: 'center', flexGrow: 1, justifyContent: 'center', - borderWidth: '1px' + borderWidth: 1 }, horizontalBox: { width: '50px' diff --git a/examples/components/MediaQueryWidget.js b/examples/components/MediaQueryWidget.js index c7c057ff..b6789a48 100644 --- a/examples/components/MediaQueryWidget.js +++ b/examples/components/MediaQueryWidget.js @@ -4,7 +4,7 @@ const styles = StyleSheet.create({ root: { alignItems: 'center', borderWidth: 1, - margin: '10px 0', + marginVertical: 10, padding: 10, textAlign: 'center' }, diff --git a/src/components/Text/TextStylePropTypes.js b/src/components/Text/TextStylePropTypes.js index 986d9915..671073cc 100644 --- a/src/components/Text/TextStylePropTypes.js +++ b/src/components/Text/TextStylePropTypes.js @@ -13,11 +13,15 @@ export default { 'letterSpacing', 'lineHeight', 'margin', + 'marginHorizontal', + 'marginVertical', 'marginBottom', 'marginLeft', 'marginRight', 'marginTop', 'padding', + 'paddingHorizontal', + 'paddingVertical', 'paddingBottom', 'paddingLeft', 'paddingRight', diff --git a/src/components/View/ViewStylePropTypes.js b/src/components/View/ViewStylePropTypes.js index 272efb7b..219347a9 100644 --- a/src/components/View/ViewStylePropTypes.js +++ b/src/components/View/ViewStylePropTypes.js @@ -54,6 +54,8 @@ export default { 'left', // margin 'margin', + 'marginHorizontal', + 'marginVertical', 'marginBottom', 'marginLeft', 'marginRight', @@ -70,6 +72,8 @@ export default { 'overflowY', // padding 'padding', + 'paddingHorizontal', + 'paddingVertical', 'paddingBottom', 'paddingLeft', 'paddingRight', diff --git a/src/modules/StylePropTypes/index.js b/src/modules/StylePropTypes/index.js index fe226527..3e08312c 100644 --- a/src/modules/StylePropTypes/index.js +++ b/src/modules/StylePropTypes/index.js @@ -7,6 +7,7 @@ export default { alignContent: string, alignItems: string, alignSelf: string, + appearance: string, backfaceVisibility: string, backgroundAttachment: string, backgroundClip: string, @@ -16,7 +17,6 @@ export default { backgroundPosition: string, backgroundRepeat: string, backgroundSize: string, - border: string, borderColor: string, borderBottomColor: string, borderLeftColor: string, @@ -61,11 +61,14 @@ export default { left: numberOrString, letterSpacing: string, lineHeight: numberOrString, + listStyle: string, margin: numberOrString, marginBottom: numberOrString, + marginHorizontal: numberOrString, marginLeft: numberOrString, marginRight: numberOrString, marginTop: numberOrString, + marginVertical: numberOrString, maxHeight: numberOrString, maxWidth: numberOrString, minHeight: numberOrString, @@ -77,13 +80,16 @@ export default { overflowY: string, padding: numberOrString, paddingBottom: numberOrString, + paddingHorizontal: numberOrString, paddingLeft: numberOrString, paddingRight: numberOrString, paddingTop: numberOrString, + paddingVertical: numberOrString, position: string, right: numberOrString, textAlign: string, textDecoration: string, + textOverflow: string, textTransform: string, top: numberOrString, userSelect: string, diff --git a/src/modules/StyleSheet/__tests__/store-test.js b/src/modules/StyleSheet/__tests__/Store-test.js similarity index 100% rename from src/modules/StyleSheet/__tests__/store-test.js rename to src/modules/StyleSheet/__tests__/Store-test.js diff --git a/src/modules/StyleSheet/__tests__/expandStyle-test.js b/src/modules/StyleSheet/__tests__/expandStyle-test.js new file mode 100644 index 00000000..381c4e8e --- /dev/null +++ b/src/modules/StyleSheet/__tests__/expandStyle-test.js @@ -0,0 +1,29 @@ +/* eslint-env mocha */ + +import assert from 'assert' +import expandStyle from '../expandStyle' + +suite('modules/StyleSheet/expandStyle', () => { + test('style property', () => { + const initial = { + borderTopWidth: 1, + borderWidth: 2, + marginTop: 50, + marginVertical: 25, + margin: 10 + } + + const expectedStyle = { + borderTopWidth: 1, + borderLeftWidth: 2, + borderRightWidth: 2, + borderBottomWidth: 2, + marginTop: 50, + marginBottom: 25, + marginLeft: 10, + marginRight: 10 + } + + assert.deepEqual(expandStyle(initial), expectedStyle) + }) +}) diff --git a/src/modules/StyleSheet/__tests__/index-test.js b/src/modules/StyleSheet/__tests__/index-test.js index e2226667..dcf16c4f 100644 --- a/src/modules/StyleSheet/__tests__/index-test.js +++ b/src/modules/StyleSheet/__tests__/index-test.js @@ -4,7 +4,7 @@ import { resetCSS, predefinedCSS } from '../predefs' import assert from 'assert' import StyleSheet from '..' -const styles = { root: { border: 0 } } +const styles = { root: { borderWidth: 1 } } suite('modules/StyleSheet', () => { setup(() => { @@ -20,14 +20,17 @@ suite('modules/StyleSheet', () => { assert.equal( StyleSheet.renderToString(), `${resetCSS}\n${predefinedCSS}\n` + - `/* 1 unique declarations */\n` + - `.border\\:0px{border:0px;}` + `/* 4 unique declarations */\n` + + `.borderBottomWidth\\:1px{border-bottom-width:1px;}\n` + + `.borderLeftWidth\\:1px{border-left-width:1px;}\n` + + `.borderRightWidth\\:1px{border-right-width:1px;}\n` + + `.borderTopWidth\\:1px{border-top-width:1px;}` ) }) test('resolve', () => { const props = { className: 'className', style: styles.root } - const expected = { className: 'className border:0px', style: {} } + const expected = { className: 'className borderTopWidth:1px borderRightWidth:1px borderBottomWidth:1px borderLeftWidth:1px', style: {} } StyleSheet.create(styles) assert.deepEqual(StyleSheet.resolve(props), expected) }) diff --git a/src/modules/StyleSheet/expandStyle.js b/src/modules/StyleSheet/expandStyle.js new file mode 100644 index 00000000..5244bb62 --- /dev/null +++ b/src/modules/StyleSheet/expandStyle.js @@ -0,0 +1,51 @@ +const styleShortHands = { + borderColor: [ 'borderTopColor', 'borderRightColor', 'borderBottomColor', 'borderLeftColor' ], + borderRadius: [ 'borderTopLeftRadius', 'borderTopRightRadius', 'borderBottomRightRadius', 'borderBottomLeftRadius' ], + borderStyle: [ 'borderTopStyle', 'borderRightStyle', 'borderBottomStyle', 'borderLeftStyle' ], + borderWidth: [ 'borderTopWidth', 'borderRightWidth', 'borderBottomWidth', 'borderLeftWidth' ], + margin: [ 'marginTop', 'marginRight', 'marginBottom', 'marginLeft' ], + marginHorizontal: [ 'marginRight', 'marginLeft' ], + marginVertical: [ 'marginTop', 'marginBottom' ], + padding: [ 'paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft' ], + paddingHorizontal: [ 'paddingRight', 'paddingLeft' ], + paddingVertical: [ 'paddingTop', 'paddingBottom' ] +} + +/** + * Alpha-sort properties, apart from shorthands which appear before the + * properties they expand into. This ensures that more specific styles override + * the shorthands, whatever the order in which they were originally declared. + */ +const sortProps = (propsArray) => propsArray.sort((a, b) => { + const expandedA = styleShortHands[a] + const expandedB = styleShortHands[b] + if (expandedA && expandedA.indexOf(b) > -1) { + return -1 + } else if (expandedB && expandedB.indexOf(a) > -1) { + return 1 + } + return a < b ? -1 : a > b ? 1 : 0 +}) + +/** + * Expand the shorthand properties to isolate every declaration from the others. + */ +const expandStyle = (style) => { + const propsArray = Object.keys(style) + const sortedProps = sortProps(propsArray) + + return sortedProps.reduce((resolvedStyle, key) => { + const expandedProps = styleShortHands[key] + const value = style[key] + if (expandedProps) { + expandedProps.forEach((prop, i) => { + resolvedStyle[expandedProps[i]] = value + }) + } else { + resolvedStyle[key] = value + } + return resolvedStyle + }, {}) +} + +export default expandStyle diff --git a/src/modules/StyleSheet/index.js b/src/modules/StyleSheet/index.js index 8345f1ba..9e41a1e8 100644 --- a/src/modules/StyleSheet/index.js +++ b/src/modules/StyleSheet/index.js @@ -1,7 +1,9 @@ import { resetCSS, predefinedCSS, predefinedClassNames } from './predefs' +import expandStyle from './expandStyle' import getStyleObjects from './getStyleObjects' import prefixer from './prefixer' import Store from './Store' +import StylePropTypes from '../StylePropTypes' /** * Initialize the store with pointer-event styles mapping to our custom pointer @@ -17,11 +19,18 @@ let store = createStore() */ 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) + const style = expandStyle(rule) + + Object.keys(style).forEach((property) => { + if (!StylePropTypes[property]) { + console.error(`ReactNativeWeb: the style property "${property}" is not supported`) + } else { + const value = style[property] + // add each declaration to the store + store.set(property, value) + } }) }) return styles @@ -49,15 +58,19 @@ const renderToString = () => { const resolve = ({ className = '', style = {} }) => { let _className let _style = {} + const expandedStyle = expandStyle(style) const classList = [ className ] - for (const prop in style) { - let styleClass = store.get(prop, style[prop]) + for (const prop in expandedStyle) { + if (!StylePropTypes[prop]) { + continue + } + let styleClass = store.get(prop, expandedStyle[prop]) if (styleClass) { classList.push(styleClass) } else { - _style[prop] = style[prop] + _style[prop] = expandedStyle[prop] } }