Simplify StyleSheet's expandStyle

This commit is contained in:
Nicolas Gallagher
2016-12-30 07:08:55 -08:00
parent 3afc5d5de6
commit 877c0d2818
10 changed files with 153 additions and 121 deletions

View File

@@ -3,45 +3,58 @@
import resolveBoxShadow from '../resolveBoxShadow';
describe('apis/StyleSheet/resolveBoxShadow', () => {
test('missing shadowColor', () => {
const style = {
shadowOffset: { width: 1, height: 2 }
};
expect(resolveBoxShadow(style)).toEqual({});
});
test('shadowColor only', () => {
const style = {
shadowColor: 'red'
};
const resolvedStyle = {};
const style = { shadowColor: 'red' };
resolveBoxShadow(resolvedStyle, style);
expect(resolveBoxShadow(style)).toEqual({
expect(resolvedStyle).toEqual({
boxShadow: '0px 0px 0px rgba(255,0,0,1)'
});
});
test('shadowColor and shadowOpacity only', () => {
const style = {
shadowColor: 'red',
shadowOpacity: 0.5
};
const resolvedStyle = {};
const style = { shadowColor: 'red', shadowOpacity: 0.5 };
resolveBoxShadow(resolvedStyle, style);
expect(resolveBoxShadow(style)).toEqual({
expect(resolvedStyle).toEqual({
boxShadow: '0px 0px 0px rgba(255,0,0,0.5)'
});
});
test('shadowOffset, shadowRadius, shadowSpread', () => {
test('shadowOffset only', () => {
const resolvedStyle = {};
const style = { shadowOffset: { width: 1, height: 2 } };
resolveBoxShadow(resolvedStyle, style);
expect(resolvedStyle).toEqual({
boxShadow: '1px 2px 0px rgba(0,0,0,0)'
});
});
test('shadowRadius only', () => {
const resolvedStyle = {};
const style = { shadowRadius: 5 };
resolveBoxShadow(resolvedStyle, style);
expect(resolvedStyle).toEqual({
boxShadow: '0px 0px 5px rgba(0,0,0,0)'
});
});
test('shadowOffset, shadowRadius, shadowColor', () => {
const resolvedStyle = {};
const style = {
shadowColor: 'rgba(50,60,70,0.5)',
shadowOffset: { width: 1, height: 2 },
shadowOpacity: 0.5,
shadowRadius: 3
};
resolveBoxShadow(resolvedStyle, style);
expect(resolveBoxShadow(style)).toEqual({
boxShadow: '2px 1px 3px rgba(50,60,70,0.25)'
expect(resolvedStyle).toEqual({
boxShadow: '1px 2px 3px rgba(50,60,70,0.25)'
});
});
});

View File

@@ -4,17 +4,16 @@ import resolveTextShadow from '../resolveTextShadow';
describe('apis/StyleSheet/resolveTextShadow', () => {
test('textShadowOffset', () => {
const resolvedStyle = {};
const style = {
textShadowColor: 'red',
textShadowOffset: { width: 2, height: 2 },
textShadowOffset: { width: 1, height: 2 },
textShadowRadius: 5
};
resolveTextShadow(resolvedStyle, style);
expect(resolveTextShadow(style)).toEqual({
textShadow: '2px 2px 5px red',
textShadowColor: null,
textShadowOffset: null,
textShadowRadius: null
expect(resolvedStyle).toEqual({
textShadow: '1px 2px 5px red'
});
});
});

View File

@@ -4,6 +4,7 @@ import resolveTransform from '../resolveTransform';
describe('apis/StyleSheet/resolveTransform', () => {
test('transform', () => {
const resolvedStyle = {};
const style = {
transform: [
{ scaleX: 20 },
@@ -11,18 +12,19 @@ describe('apis/StyleSheet/resolveTransform', () => {
{ rotate: '20deg' }
]
};
resolveTransform(resolvedStyle, style);
expect(resolveTransform(style)).toEqual({ transform: 'scaleX(20) translateX(20px) rotate(20deg)' });
expect(resolvedStyle).toEqual({
transform: 'scaleX(20) translateX(20px) rotate(20deg)' });
});
test('transformMatrix', () => {
const style = {
transformMatrix: [ 1, 2, 3, 4, 5, 6 ]
};
const resolvedStyle = {};
const style = { transformMatrix: [ 1, 2, 3, 4, 5, 6 ] };
resolveTransform(resolvedStyle, style);
expect(resolveTransform(style)).toEqual({
transform: 'matrix3d(1,2,3,4,5,6)',
transformMatrix: null
expect(resolvedStyle).toEqual({
transform: 'matrix3d(1,2,3,4,5,6)'
});
});
});

View File

@@ -1,21 +1,9 @@
import expandStyle from './expandStyle';
import flattenStyle from './flattenStyle';
import i18nStyle from './i18nStyle';
import resolveBoxShadow from './resolveBoxShadow';
import resolveTextShadow from './resolveTextShadow';
import resolveTransform from './resolveTransform';
import resolveVendorPrefixes from './resolveVendorPrefixes';
const processors = [
resolveBoxShadow,
resolveTextShadow,
resolveTransform,
resolveVendorPrefixes
];
const applyProcessors = (style) => processors.reduce((style, processor) => processor(style), style);
const createReactDOMStyle = (reactNativeStyle) => applyProcessors(
const createReactDOMStyle = (reactNativeStyle) => resolveVendorPrefixes(
expandStyle(i18nStyle(flattenStyle(reactNativeStyle)))
);

View File

@@ -10,6 +10,9 @@
*/
import normalizeValue from './normalizeValue';
import resolveBoxShadow from './resolveBoxShadow';
import resolveTextShadow from './resolveTextShadow';
import resolveTransform from './resolveTransform';
const emptyObject = {};
const styleShortFormProperties = {
@@ -28,46 +31,83 @@ const styleShortFormProperties = {
writingDirection: [ 'direction' ]
};
const alphaSort = (arr) => arr.sort((a, b) => {
const alphaSortProps = (propsArray) => propsArray.sort((a, b) => {
if (a < b) { return -1; }
if (a > b) { return 1; }
return 0;
});
const createStyleReducer = (originalStyle) => {
const originalStyleProps = Object.keys(originalStyle);
const expandStyle = (style) => {
if (!style) { return emptyObject; }
const styleProps = Object.keys(style);
const sortedStyleProps = alphaSortProps(styleProps);
let hasResolvedBoxShadow = false;
let hasResolvedTextShadow = false;
return (style, prop) => {
const value = normalizeValue(prop, originalStyle[prop]);
const longFormProperties = styleShortFormProperties[prop];
const reducer = (resolvedStyle, prop) => {
const value = normalizeValue(prop, style[prop]);
if (value == null) { return resolvedStyle; }
// React Native treats `flex:1` like `flex:1 1 auto`
if (prop === 'flex') {
style.flexGrow = value;
style.flexShrink = 1;
style.flexBasis = 'auto';
// React Native accepts 'center' as a value
} else if (prop === 'textAlignVertical') {
style.verticalAlign = (value === 'center' ? 'middle' : value);
} else if (longFormProperties) {
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;
switch (prop) {
// ignore React Native styles
case 'elevation':
case 'resizeMode': {
break;
}
case 'flex': {
resolvedStyle.flexGrow = value;
resolvedStyle.flexShrink = 1;
resolvedStyle.flexBasis = 'auto';
break;
}
case 'shadowColor':
case 'shadowOffset':
case 'shadowOpacity':
case 'shadowRadius': {
if (!hasResolvedBoxShadow) {
resolveBoxShadow(resolvedStyle, style);
}
});
} else {
style[prop] = value;
hasResolvedBoxShadow = true;
break;
}
case 'textAlignVertical': {
resolvedStyle.verticalAlign = (value === 'center' ? 'middle' : value);
break;
}
case 'textShadowColor':
case 'textShadowOffset':
case 'textShadowRadius': {
if (!hasResolvedTextShadow) {
resolveTextShadow(resolvedStyle, style);
}
hasResolvedTextShadow = true;
break;
}
case 'transform': {
resolveTransform(resolvedStyle, style);
break;
}
default: {
const longFormProperties = styleShortFormProperties[prop];
if (longFormProperties) {
longFormProperties.forEach((longForm, i) => {
// the value of any longform property in the original styles takes
// precedence over the shortform's value
if (styleProps.indexOf(longForm) === -1) {
resolvedStyle[longForm] = value;
}
});
} else {
resolvedStyle[prop] = value;
}
}
}
return style;
};
};
const expandStyle = (style = emptyObject) => {
const sortedStyleProps = alphaSort(Object.keys(style));
const styleReducer = createStyleReducer(style);
return sortedStyleProps.reduce(styleReducer, {});
return resolvedStyle;
};
const resolvedStyle = sortedStyleProps.reduce(reducer, {});
return resolvedStyle;
};
module.exports = expandStyle;

View File

@@ -1,9 +1,9 @@
import normalizeColor from '../../modules/normalizeColor';
import normalizeValue from './normalizeValue';
const applyOpacity = (color, opacity) => {
const normalizedColor = normalizeColor(color);
const colorNumber = normalizedColor === null ? 0x00000000 : normalizedColor;
const defaultOffset = { height: 0, width: 0 };
const applyOpacity = (colorNumber, opacity) => {
const r = (colorNumber & 0xff000000) >>> 24;
const g = (colorNumber & 0x00ff0000) >>> 16;
const b = (colorNumber & 0x0000ff00) >>> 8;
@@ -12,22 +12,18 @@ const applyOpacity = (color, opacity) => {
};
// TODO: add inset and spread support
const resolveBoxShadow = (style) => {
if (style && style.shadowColor) {
const { height, width } = style.shadowOffset || {};
const opacity = style.shadowOpacity != null ? style.shadowOpacity : 1;
const color = applyOpacity(style.shadowColor, opacity);
const blurRadius = normalizeValue(null, style.shadowRadius || 0);
const offsetX = normalizeValue(null, height || 0);
const offsetY = normalizeValue(null, width || 0);
const boxShadow = `${offsetX} ${offsetY} ${blurRadius} ${color}`;
style.boxShadow = style.boxShadow ? `${style.boxShadow}, ${boxShadow}` : boxShadow;
}
delete style.shadowColor;
delete style.shadowOffset;
delete style.shadowOpacity;
delete style.shadowRadius;
return style;
const resolveBoxShadow = (resolvedStyle, style) => {
const { height, width } = style.shadowOffset || defaultOffset;
const offsetX = normalizeValue(null, width);
const offsetY = normalizeValue(null, height);
const blurRadius = normalizeValue(null, style.shadowRadius || 0);
// rgba color
const opacity = style.shadowOpacity != null ? style.shadowOpacity : 1;
const colorNumber = normalizeColor(style.shadowColor) || 0x00000000;
const color = applyOpacity(colorNumber, opacity);
const boxShadow = `${offsetX} ${offsetY} ${blurRadius} ${color}`;
resolvedStyle.boxShadow = style.boxShadow ? `${style.boxShadow}, ${boxShadow}` : boxShadow;
};
module.exports = resolveBoxShadow;

View File

@@ -1,19 +1,15 @@
import normalizeValue from './normalizeValue';
const resolveTextShadow = (style) => {
if (style && style.textShadowOffset) {
const { height, width } = style.textShadowOffset;
const offsetX = normalizeValue(null, height || 0);
const offsetY = normalizeValue(null, width || 0);
const blurRadius = normalizeValue(null, style.textShadowRadius || 0);
const color = style.textShadowColor || 'currentcolor';
const defaultOffset = { height: 0, width: 0 };
style.textShadow = `${offsetX} ${offsetY} ${blurRadius} ${color}`;
style.textShadowColor = null;
style.textShadowOffset = null;
style.textShadowRadius = null;
}
return style;
const resolveTextShadow = (resolvedStyle, style) => {
const { height, width } = style.textShadowOffset || defaultOffset;
const offsetX = normalizeValue(null, width);
const offsetY = normalizeValue(null, height);
const blurRadius = normalizeValue(null, style.textShadowRadius || 0);
const color = style.textShadowColor || 'currentcolor';
resolvedStyle.textShadow = `${offsetX} ${offsetY} ${blurRadius} ${color}`;
};
module.exports = resolveTextShadow;

View File

@@ -14,16 +14,14 @@ const convertTransformMatrix = (transformMatrix) => {
return `matrix3d(${matrix})`;
};
const resolveTransform = (style) => {
if (style) {
if (style.transform && Array.isArray(style.transform)) {
style.transform = style.transform.map(mapTransform).join(' ');
} else if (style.transformMatrix) {
style.transform = convertTransformMatrix(style.transformMatrix);
style.transformMatrix = null;
}
const resolveTransform = (resolvedStyle, style) => {
if (Array.isArray(style.transform)) {
const transform = style.transform.map(mapTransform).join(' ');
resolvedStyle.transform = transform;
} else if (style.transformMatrix) {
const transform = convertTransformMatrix(style.transformMatrix)
resolvedStyle.transform = transform;
}
return style;
};
module.exports = resolveTransform;

View File

@@ -2,14 +2,16 @@ import prefixAll from 'inline-style-prefixer/static';
const resolveVendorPrefixes = (style) => {
const prefixedStyles = prefixAll(style);
// React@15 removed undocumented support for fallback values in
// inline-styles. Revert array values to the standard CSS value
for (const prop in prefixedStyles) {
Object.keys(prefixedStyles).forEach((prop) => {
const value = prefixedStyles[prop];
if (Array.isArray(value)) {
prefixedStyles[prop] = value[value.length - 1];
}
}
});
return prefixedStyles;
};

View File

@@ -438,7 +438,6 @@ exports[`components/Image prop "defaultSource" sets background image when value
"flexDirection": "column",
"flexShrink": 0,
"font": "inherit",
"height": undefined,
"listStyle": "none",
"marginBottom": "0px",
"marginLeft": "0px",
@@ -457,7 +456,6 @@ exports[`components/Image prop "defaultSource" sets background image when value
"position": "relative",
"textAlign": "inherit",
"textDecoration": "none",
"width": undefined,
}
} />
`;