mirror of
https://github.com/zoriya/react-native-web.git
synced 2026-06-03 02:42:05 +00:00
[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:
@@ -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'
|
||||||
|
|||||||
@@ -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,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)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user