[fix] StyleSheet: shorthand properties

Expand shorthand properties to preserve the one-rule-to-one-style
isolation. Resolve styles like React Native - most specific comes last.
Add support for the hz and vt properties for margin and padding.

Fix #40
This commit is contained in:
Nicolas Gallagher
2015-12-13 12:06:46 -08:00
parent e1da11fa1d
commit 501c19fe9b
10 changed files with 126 additions and 15 deletions
+3 -2
View File
@@ -213,7 +213,8 @@ export default class App extends React.Component {
const styles = StyleSheet.create({ const styles = StyleSheet.create({
root: { root: {
common: { common: {
margin: '0 auto' marginVertical: 0,
marginHorizontal: 'auto'
}, },
mqSmall: { mqSmall: {
maxWidth: '400px' maxWidth: '400px'
@@ -230,7 +231,7 @@ const styles = StyleSheet.create({
alignItems: 'center', alignItems: 'center',
flexGrow: 1, flexGrow: 1,
justifyContent: 'center', justifyContent: 'center',
borderWidth: '1px' borderWidth: 1
}, },
horizontalBox: { horizontalBox: {
width: '50px' width: '50px'
+1 -1
View File
@@ -4,7 +4,7 @@ const styles = StyleSheet.create({
root: { root: {
alignItems: 'center', alignItems: 'center',
borderWidth: 1, borderWidth: 1,
margin: '10px 0', marginVertical: 10,
padding: 10, padding: 10,
textAlign: 'center' textAlign: 'center'
}, },
@@ -13,11 +13,15 @@ export default {
'letterSpacing', 'letterSpacing',
'lineHeight', 'lineHeight',
'margin', 'margin',
'marginHorizontal',
'marginVertical',
'marginBottom', 'marginBottom',
'marginLeft', 'marginLeft',
'marginRight', 'marginRight',
'marginTop', 'marginTop',
'padding', 'padding',
'paddingHorizontal',
'paddingVertical',
'paddingBottom', 'paddingBottom',
'paddingLeft', 'paddingLeft',
'paddingRight', 'paddingRight',
@@ -54,6 +54,8 @@ export default {
'left', 'left',
// margin // margin
'margin', 'margin',
'marginHorizontal',
'marginVertical',
'marginBottom', 'marginBottom',
'marginLeft', 'marginLeft',
'marginRight', 'marginRight',
@@ -70,6 +72,8 @@ export default {
'overflowY', 'overflowY',
// padding // padding
'padding', 'padding',
'paddingHorizontal',
'paddingVertical',
'paddingBottom', 'paddingBottom',
'paddingLeft', 'paddingLeft',
'paddingRight', 'paddingRight',
+7 -1
View File
@@ -7,6 +7,7 @@ export default {
alignContent: string, alignContent: string,
alignItems: string, alignItems: string,
alignSelf: string, alignSelf: string,
appearance: string,
backfaceVisibility: string, backfaceVisibility: string,
backgroundAttachment: string, backgroundAttachment: string,
backgroundClip: string, backgroundClip: string,
@@ -16,7 +17,6 @@ export default {
backgroundPosition: string, backgroundPosition: string,
backgroundRepeat: string, backgroundRepeat: string,
backgroundSize: string, backgroundSize: string,
border: string,
borderColor: string, borderColor: string,
borderBottomColor: string, borderBottomColor: string,
borderLeftColor: string, borderLeftColor: string,
@@ -61,11 +61,14 @@ export default {
left: numberOrString, left: numberOrString,
letterSpacing: string, letterSpacing: string,
lineHeight: numberOrString, lineHeight: numberOrString,
listStyle: string,
margin: numberOrString, margin: numberOrString,
marginBottom: numberOrString, marginBottom: numberOrString,
marginHorizontal: numberOrString,
marginLeft: numberOrString, marginLeft: numberOrString,
marginRight: numberOrString, marginRight: numberOrString,
marginTop: numberOrString, marginTop: numberOrString,
marginVertical: numberOrString,
maxHeight: numberOrString, maxHeight: numberOrString,
maxWidth: numberOrString, maxWidth: numberOrString,
minHeight: numberOrString, minHeight: numberOrString,
@@ -77,13 +80,16 @@ export default {
overflowY: string, overflowY: string,
padding: numberOrString, padding: numberOrString,
paddingBottom: numberOrString, paddingBottom: numberOrString,
paddingHorizontal: numberOrString,
paddingLeft: numberOrString, paddingLeft: numberOrString,
paddingRight: numberOrString, paddingRight: numberOrString,
paddingTop: numberOrString, paddingTop: numberOrString,
paddingVertical: numberOrString,
position: string, position: string,
right: numberOrString, right: numberOrString,
textAlign: string, textAlign: string,
textDecoration: string, textDecoration: string,
textOverflow: string,
textTransform: string, textTransform: string,
top: numberOrString, top: numberOrString,
userSelect: string, userSelect: string,
@@ -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)
})
})
@@ -4,7 +4,7 @@ import { resetCSS, predefinedCSS } from '../predefs'
import assert from 'assert' import assert from 'assert'
import StyleSheet from '..' import StyleSheet from '..'
const styles = { root: { border: 0 } } const styles = { root: { borderWidth: 1 } }
suite('modules/StyleSheet', () => { suite('modules/StyleSheet', () => {
setup(() => { setup(() => {
@@ -20,14 +20,17 @@ suite('modules/StyleSheet', () => {
assert.equal( assert.equal(
StyleSheet.renderToString(), StyleSheet.renderToString(),
`${resetCSS}\n${predefinedCSS}\n` + `${resetCSS}\n${predefinedCSS}\n` +
`/* 1 unique declarations */\n` + `/* 4 unique declarations */\n` +
`.border\\:0px{border:0px;}` `.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', () => { test('resolve', () => {
const props = { className: 'className', style: styles.root } 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) StyleSheet.create(styles)
assert.deepEqual(StyleSheet.resolve(props), expected) assert.deepEqual(StyleSheet.resolve(props), expected)
}) })
+51
View File
@@ -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
+18 -5
View File
@@ -1,7 +1,9 @@
import { resetCSS, predefinedCSS, predefinedClassNames } from './predefs' import { resetCSS, predefinedCSS, predefinedClassNames } from './predefs'
import expandStyle from './expandStyle'
import getStyleObjects from './getStyleObjects' import getStyleObjects from './getStyleObjects'
import prefixer from './prefixer' import prefixer from './prefixer'
import Store from './Store' import Store from './Store'
import StylePropTypes from '../StylePropTypes'
/** /**
* Initialize the store with pointer-event styles mapping to our custom pointer * 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 create = (styles: Object): Object => {
const rules = getStyleObjects(styles) const rules = getStyleObjects(styles)
rules.forEach((rule) => { rules.forEach((rule) => {
Object.keys(rule).forEach(property => { const style = expandStyle(rule)
const value = rule[property]
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 // add each declaration to the store
store.set(property, value) store.set(property, value)
}
}) })
}) })
return styles return styles
@@ -49,15 +58,19 @@ const renderToString = () => {
const resolve = ({ className = '', style = {} }) => { const resolve = ({ className = '', style = {} }) => {
let _className let _className
let _style = {} let _style = {}
const expandedStyle = expandStyle(style)
const classList = [ className ] const classList = [ className ]
for (const prop in style) { for (const prop in expandedStyle) {
let styleClass = store.get(prop, style[prop]) if (!StylePropTypes[prop]) {
continue
}
let styleClass = store.get(prop, expandedStyle[prop])
if (styleClass) { if (styleClass) {
classList.push(styleClass) classList.push(styleClass)
} else { } else {
_style[prop] = style[prop] _style[prop] = expandedStyle[prop]
} }
} }