[fix] StyleSheet expansion of shortform properties

The previous implementation relied on a buggy sorting strategy. It could
result in shortform properties replacing the values set for longform
properties. This patch avoids expanding shorthand values to a longform
property if it declared in the original style object.

Fix #141
This commit is contained in:
Nicolas Gallagher
2016-07-05 19:09:16 -07:00
parent 579bdeb8a5
commit d4d67dafc0
2 changed files with 72 additions and 44 deletions

View File

@@ -4,20 +4,29 @@ import assert from 'assert'
import expandStyle from '../expandStyle' import expandStyle from '../expandStyle'
suite('apis/StyleSheet/expandStyle', () => { suite('apis/StyleSheet/expandStyle', () => {
test('style resolution', () => { test('shortform -> longform', () => {
const initial = { const initial = {
borderTopWidth: 1, borderStyle: 'solid',
borderWidth: 2, boxSizing: 'border-box',
borderBottomColor: 'white',
borderBottomWidth: 1,
borderWidth: 0,
marginTop: 50, marginTop: 50,
marginVertical: 25, marginVertical: 25,
margin: 10 margin: 10
} }
const expected = { const expected = {
borderTopWidth: '1px', borderBottomStyle: 'solid',
borderLeftWidth: '2px', borderLeftStyle: 'solid',
borderRightWidth: '2px', borderRightStyle: 'solid',
borderBottomWidth: '2px', boxSizing: 'border-box',
borderBottomColor: 'white',
borderTopStyle: 'solid',
borderTopWidth: '0px',
borderLeftWidth: '0px',
borderRightWidth: '0px',
borderBottomWidth: '1px',
marginTop: '50px', marginTop: '50px',
marginBottom: '25px', marginBottom: '25px',
marginLeft: '10px', marginLeft: '10px',
@@ -27,6 +36,18 @@ suite('apis/StyleSheet/expandStyle', () => {
assert.deepEqual(expandStyle(initial), expected) assert.deepEqual(expandStyle(initial), expected)
}) })
test('textAlignVertical', () => {
const initial = {
textAlignVertical: 'center'
}
const expected = {
verticalAlign: 'middle'
}
assert.deepEqual(expandStyle(initial), expected)
})
test('flex', () => { test('flex', () => {
const value = 10 const value = 10

View File

@@ -1,6 +1,17 @@
/**
* The browser implements the CSS cascade, where the order of properties is a
* factor in determining which styles to paint. React Native is different in
* giving precedence to the more specific styles. For example, the value of
* `paddingTop` takes precedence over that of `padding`.
*
* This module creates mutally exclusive style declarations by expanding all of
* React Native's supported shortform properties (e.g. `padding`) to their
* longfrom equivalents.
*/
import normalizeValue from './normalizeValue' import normalizeValue from './normalizeValue'
const styleShortHands = { const styleShortFormProperties = {
borderColor: [ 'borderTopColor', 'borderRightColor', 'borderBottomColor', 'borderLeftColor' ], borderColor: [ 'borderTopColor', 'borderRightColor', 'borderBottomColor', 'borderLeftColor' ],
borderRadius: [ 'borderTopLeftRadius', 'borderTopRightRadius', 'borderBottomRightRadius', 'borderBottomLeftRadius' ], borderRadius: [ 'borderTopLeftRadius', 'borderTopRightRadius', 'borderBottomRightRadius', 'borderBottomLeftRadius' ],
borderStyle: [ 'borderTopStyle', 'borderRightStyle', 'borderBottomStyle', 'borderLeftStyle' ], borderStyle: [ 'borderTopStyle', 'borderRightStyle', 'borderBottomStyle', 'borderLeftStyle' ],
@@ -16,50 +27,46 @@ const styleShortHands = {
writingDirection: [ 'direction' ] writingDirection: [ 'direction' ]
} }
/** const alphaSort = (arr) => arr.sort((a, b) => {
* Alpha-sort properties, apart from shorthands they must appear before the if (a < b) { return -1 }
* longhand properties that they expand into. This lets more specific styles if (a > b) { return 1 }
* override less specific styles, whatever the order in which they were return 0
* 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
}) })
/** const createStyleReducer = (originalStyle) => {
* Expand the shorthand properties to isolate every declaration from the others. const originalStyleProps = Object.keys(originalStyle)
*/
const expandStyle = (style) => {
const propsArray = Object.keys(style)
const sortedProps = sortProps(propsArray)
return sortedProps.reduce((resolvedStyle, key) => { return (style, prop) => {
const expandedProps = styleShortHands[key] const value = normalizeValue(prop, originalStyle[prop])
const value = normalizeValue(key, style[key]) const longFormProperties = styleShortFormProperties[prop]
// React Native treats `flex:1` like `flex:1 1 auto` // React Native treats `flex:1` like `flex:1 1 auto`
if (key === 'flex') { if (prop === 'flex') {
resolvedStyle.flexGrow = value style.flexGrow = value
resolvedStyle.flexShrink = 1 if (style.flexShrink == null) { style.flexShrink = 1 }
resolvedStyle.flexBasis = 'auto' if (style.flexBasis == null) { style.flexBasis = 'auto' }
} else if (key === 'textAlignVertical') { // React Native accepts 'center' as a value
resolvedStyle.verticalAlign = (value === 'center' ? 'middle' : value) } else if (prop === 'textAlignVertical') {
} else if (expandedProps) { style.verticalAlign = (value === 'center' ? 'middle' : value)
expandedProps.forEach((prop, i) => { } else if (longFormProperties) {
resolvedStyle[expandedProps[i]] = value longFormProperties.forEach((longForm, i) => {
// the value of any longform property in the original styles takes
// precedence over the shortform's value
if (originalStyleProps.indexOf(longForm) === -1) {
style[longForm] = value
}
}) })
} else { } else {
resolvedStyle[key] = value style[prop] = value
} }
return resolvedStyle return style
}, {}) }
}
const expandStyle = (style) => {
const sortedStyleProps = alphaSort(Object.keys(style))
const styleReducer = createStyleReducer(style)
return sortedStyleProps.reduce(styleReducer, {})
} }
module.exports = expandStyle module.exports = expandStyle