mirror of
https://github.com/zhigang1992/react-native-web.git
synced 2026-04-28 20:34:52 +08:00
[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:
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user