From 877c0d28183f63c5d95327a79ce6f10a021b2dd5 Mon Sep 17 00:00:00 2001 From: Nicolas Gallagher Date: Fri, 30 Dec 2016 07:08:55 -0800 Subject: [PATCH] Simplify StyleSheet's expandStyle --- .../__tests__/resolveBoxShadow-test.js | 53 ++++++---- .../__tests__/resolveTextShadow-test.js | 11 +- .../__tests__/resolveTransform-test.js | 16 +-- src/apis/StyleSheet/createReactDOMStyle.js | 14 +-- src/apis/StyleSheet/expandStyle.js | 100 ++++++++++++------ src/apis/StyleSheet/resolveBoxShadow.js | 34 +++--- src/apis/StyleSheet/resolveTextShadow.js | 22 ++-- src/apis/StyleSheet/resolveTransform.js | 16 ++- src/apis/StyleSheet/resolveVendorPrefixes.js | 6 +- .../__snapshots__/index-test.js.snap | 2 - 10 files changed, 153 insertions(+), 121 deletions(-) diff --git a/src/apis/StyleSheet/__tests__/resolveBoxShadow-test.js b/src/apis/StyleSheet/__tests__/resolveBoxShadow-test.js index 949c2798..51de4820 100644 --- a/src/apis/StyleSheet/__tests__/resolveBoxShadow-test.js +++ b/src/apis/StyleSheet/__tests__/resolveBoxShadow-test.js @@ -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)' }); }); }); diff --git a/src/apis/StyleSheet/__tests__/resolveTextShadow-test.js b/src/apis/StyleSheet/__tests__/resolveTextShadow-test.js index 491a0953..56ef66cd 100644 --- a/src/apis/StyleSheet/__tests__/resolveTextShadow-test.js +++ b/src/apis/StyleSheet/__tests__/resolveTextShadow-test.js @@ -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' }); }); }); diff --git a/src/apis/StyleSheet/__tests__/resolveTransform-test.js b/src/apis/StyleSheet/__tests__/resolveTransform-test.js index 334f3aec..a7143af6 100644 --- a/src/apis/StyleSheet/__tests__/resolveTransform-test.js +++ b/src/apis/StyleSheet/__tests__/resolveTransform-test.js @@ -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)' }); }); }); diff --git a/src/apis/StyleSheet/createReactDOMStyle.js b/src/apis/StyleSheet/createReactDOMStyle.js index bfce5de4..a8bb4747 100644 --- a/src/apis/StyleSheet/createReactDOMStyle.js +++ b/src/apis/StyleSheet/createReactDOMStyle.js @@ -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))) ); diff --git a/src/apis/StyleSheet/expandStyle.js b/src/apis/StyleSheet/expandStyle.js index 410d702a..9e628ccb 100644 --- a/src/apis/StyleSheet/expandStyle.js +++ b/src/apis/StyleSheet/expandStyle.js @@ -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; diff --git a/src/apis/StyleSheet/resolveBoxShadow.js b/src/apis/StyleSheet/resolveBoxShadow.js index 4eae6b06..4840cd36 100644 --- a/src/apis/StyleSheet/resolveBoxShadow.js +++ b/src/apis/StyleSheet/resolveBoxShadow.js @@ -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; diff --git a/src/apis/StyleSheet/resolveTextShadow.js b/src/apis/StyleSheet/resolveTextShadow.js index b0fd7689..f6e19d19 100644 --- a/src/apis/StyleSheet/resolveTextShadow.js +++ b/src/apis/StyleSheet/resolveTextShadow.js @@ -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; diff --git a/src/apis/StyleSheet/resolveTransform.js b/src/apis/StyleSheet/resolveTransform.js index 5d3e631d..5db83942 100644 --- a/src/apis/StyleSheet/resolveTransform.js +++ b/src/apis/StyleSheet/resolveTransform.js @@ -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; diff --git a/src/apis/StyleSheet/resolveVendorPrefixes.js b/src/apis/StyleSheet/resolveVendorPrefixes.js index 015d525a..dfdddbde 100644 --- a/src/apis/StyleSheet/resolveVendorPrefixes.js +++ b/src/apis/StyleSheet/resolveVendorPrefixes.js @@ -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; }; diff --git a/src/components/Image/__tests__/__snapshots__/index-test.js.snap b/src/components/Image/__tests__/__snapshots__/index-test.js.snap index 9c25aab8..994f7f64 100644 --- a/src/components/Image/__tests__/__snapshots__/index-test.js.snap +++ b/src/components/Image/__tests__/__snapshots__/index-test.js.snap @@ -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, } } /> `;