diff --git a/src/apis/StyleSheet/StyleRegistry.js b/src/apis/StyleSheet/StyleRegistry.js index c994ef84..9b2a2714 100644 --- a/src/apis/StyleSheet/StyleRegistry.js +++ b/src/apis/StyleSheet/StyleRegistry.js @@ -6,6 +6,7 @@ import createReactDOMStyle from './createReactDOMStyle'; import flattenArray from '../../modules/flattenArray'; import flattenStyle from './flattenStyle'; import I18nManager from '../I18nManager'; +import i18nStyle from './i18nStyle'; import mapKeyValue from '../../modules/mapKeyValue'; import { prefixInlineStyles } from '../../modules/prefixStyles'; import ReactNativePropRegistry from '../../modules/ReactNativePropRegistry'; @@ -34,7 +35,7 @@ class StyleRegistry { register(flatStyle) { const id = ReactNativePropRegistry.register(flatStyle); const key = createCacheKey(id); - const style = createReactDOMStyle(flatStyle); + const style = createReactDOMStyle(i18nStyle(flatStyle)); const classList = mapKeyValue(style, (prop, value) => { if (value != null) { return this.styleManager.setDeclaration(prop, value); @@ -130,7 +131,7 @@ class StyleRegistry { * Resolves a React Native style object */ _resolveStyle(reactNativeStyle) { - const domStyle = createReactDOMStyle(flattenStyle(reactNativeStyle)); + const domStyle = createReactDOMStyle(i18nStyle(flattenStyle(reactNativeStyle))); const props = Object.keys(domStyle).reduce( (props, styleProp) => { diff --git a/src/apis/StyleSheet/__tests__/__snapshots__/createReactDOMStyle-test.js.snap b/src/apis/StyleSheet/__tests__/__snapshots__/createReactDOMStyle-test.js.snap index 5e222a84..4f5cabf9 100644 --- a/src/apis/StyleSheet/__tests__/__snapshots__/createReactDOMStyle-test.js.snap +++ b/src/apis/StyleSheet/__tests__/__snapshots__/createReactDOMStyle-test.js.snap @@ -1,18 +1,20 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`apis/StyleSheet/createReactDOMStyle converts ReactNative style to ReactDOM style 1`] = ` +exports[`apis/StyleSheet/createReactDOMStyle shortform -> longform 1`] = ` Object { + "borderBottomColor": "white", + "borderBottomStyle": "solid", "borderBottomWidth": "1px", - "borderLeftWidth": "1px", - "borderRightWidth": "1px", - "borderTopWidth": "1px", - "borderWidthLeft": "2px", - "borderWidthRight": "3px", - "boxShadow": "1px 1px 1px 1px #000, 1px 2px 0px red", - "display": "flex", - "flexShrink": 0, - "marginBottom": "0px", - "marginTop": "0px", - "opacity": 0, + "borderLeftStyle": "solid", + "borderLeftWidth": "0px", + "borderRightStyle": "solid", + "borderRightWidth": "0px", + "borderTopStyle": "solid", + "borderTopWidth": "0px", + "boxSizing": "border-box", + "marginBottom": "25px", + "marginLeft": "10px", + "marginRight": "10px", + "marginTop": "50px", } `; diff --git a/src/apis/StyleSheet/__tests__/__snapshots__/expandStyle-test.js.snap b/src/apis/StyleSheet/__tests__/__snapshots__/expandStyle-test.js.snap deleted file mode 100644 index 40ae6d4c..00000000 --- a/src/apis/StyleSheet/__tests__/__snapshots__/expandStyle-test.js.snap +++ /dev/null @@ -1,20 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`apis/StyleSheet/expandStyle shortform -> longform 1`] = ` -Object { - "borderBottomColor": "white", - "borderBottomStyle": "solid", - "borderBottomWidth": "1px", - "borderLeftStyle": "solid", - "borderLeftWidth": "0px", - "borderRightStyle": "solid", - "borderRightWidth": "0px", - "borderTopStyle": "solid", - "borderTopWidth": "0px", - "boxSizing": "border-box", - "marginBottom": "25px", - "marginLeft": "10px", - "marginRight": "10px", - "marginTop": "50px", -} -`; diff --git a/src/apis/StyleSheet/__tests__/createReactDOMStyle-test.js b/src/apis/StyleSheet/__tests__/createReactDOMStyle-test.js index 7955be07..b16d0425 100644 --- a/src/apis/StyleSheet/__tests__/createReactDOMStyle-test.js +++ b/src/apis/StyleSheet/__tests__/createReactDOMStyle-test.js @@ -16,13 +16,148 @@ const reactNativeStyle = { }; describe('apis/StyleSheet/createReactDOMStyle', () => { - test('converts ReactNative style to ReactDOM style', () => { - expect(createReactDOMStyle(reactNativeStyle)).toMatchSnapshot(); - }); - test('noop on DOM styles', () => { const firstStyle = createReactDOMStyle(reactNativeStyle); const secondStyle = createReactDOMStyle(firstStyle); expect(firstStyle).toEqual(secondStyle); }); + + test('flex', () => { + expect(createReactDOMStyle({ display: 'flex' })).toEqual({ + display: 'flex', + flexShrink: 0 + }); + + expect(createReactDOMStyle({ display: 'flex', flex: 1 })).toEqual({ + display: 'flex', + flexGrow: 1, + flexShrink: 1, + flexBasis: 'auto' + }); + + expect(createReactDOMStyle({ display: 'flex', flex: 10 })).toEqual({ + display: 'flex', + flexGrow: 10, + flexShrink: 1, + flexBasis: 'auto' + }); + + expect(createReactDOMStyle({ display: 'flex', flexShrink: 1 })).toEqual({ + display: 'flex', + flexShrink: 1 + }); + + expect(createReactDOMStyle({ display: 'flex', flex: 1, flexShrink: 2 })).toEqual({ + display: 'flex', + flexGrow: 1, + flexShrink: 2, + flexBasis: 'auto' + }); + }); + + test('shortform -> longform', () => { + const style = { + borderStyle: 'solid', + boxSizing: 'border-box', + borderBottomColor: 'white', + borderBottomWidth: 1, + borderWidth: 0, + marginTop: 50, + marginVertical: 25, + margin: 10 + }; + + expect(createReactDOMStyle(style)).toMatchSnapshot(); + }); + + describe('shadow styles', () => { + test('shadowColor only', () => { + const style = { shadowColor: 'red' }; + const resolved = createReactDOMStyle(style); + + expect(resolved).toEqual({ + boxShadow: '0px 0px 0px red' + }); + }); + + test('shadowColor and shadowOpacity only', () => { + expect(createReactDOMStyle({ shadowColor: 'red', shadowOpacity: 0.5 })).toEqual({ + boxShadow: '0px 0px 0px rgba(255,0,0,0.5)' + }); + }); + + test('shadowOffset only', () => { + expect(createReactDOMStyle({ shadowOffset: { width: 1, height: 2 } })).toEqual({}); + }); + + test('shadowRadius only', () => { + expect(createReactDOMStyle({ shadowRadius: 5 })).toEqual({}); + }); + + test('shadowOffset, shadowRadius, shadowColor', () => { + expect( + createReactDOMStyle({ + shadowColor: 'rgba(50,60,70,0.5)', + shadowOffset: { width: 1, height: 2 }, + shadowOpacity: 0.5, + shadowRadius: 3 + }) + ).toEqual({ + boxShadow: '1px 2px 3px rgba(50,60,70,0.25)' + }); + }); + }); + + test('textAlignVertical', () => { + expect( + createReactDOMStyle({ + textAlignVertical: 'center' + }) + ).toEqual({ + verticalAlign: 'middle' + }); + }); + + test('textShadowOffset', () => { + expect( + createReactDOMStyle({ + textShadowColor: 'red', + textShadowOffset: { width: 1, height: 2 }, + textShadowRadius: 5 + }) + ).toEqual({ + textShadow: '1px 2px 5px red' + }); + }); + + describe('transform', () => { + // passthrough if transform value is ever a string + test('string', () => { + const transform = 'perspective(50px) scaleX(20) translateX(20px) rotate(20deg)'; + const style = { transform }; + const resolved = createReactDOMStyle(style); + + expect(resolved).toEqual({ transform }); + }); + + test('array', () => { + const style = { + transform: [{ perspective: 50 }, { scaleX: 20 }, { translateX: 20 }, { rotate: '20deg' }] + }; + const resolved = createReactDOMStyle(style); + + expect(resolved).toEqual({ + transform: 'perspective(50px) scaleX(20) translateX(20px) rotate(20deg)' + }); + }); + + test('transformMatrix', () => { + const style = { transformMatrix: [1, 2, 3, 4, 5, 6] }; + const resolved = createReactDOMStyle(style); + + expect(resolved).toEqual({ + transform: 'matrix3d(1,2,3,4,5,6)' + }); + }); + }); }); diff --git a/src/apis/StyleSheet/__tests__/expandStyle-test.js b/src/apis/StyleSheet/__tests__/expandStyle-test.js deleted file mode 100644 index 47921bca..00000000 --- a/src/apis/StyleSheet/__tests__/expandStyle-test.js +++ /dev/null @@ -1,81 +0,0 @@ -/* eslint-env jasmine, jest */ - -import expandStyle from '../expandStyle'; - -describe('apis/StyleSheet/expandStyle', () => { - test('shortform -> longform', () => { - const style = { - borderStyle: 'solid', - boxSizing: 'border-box', - borderBottomColor: 'white', - borderBottomWidth: 1, - borderWidth: 0, - marginTop: 50, - marginVertical: 25, - margin: 10 - }; - - expect(expandStyle(style)).toMatchSnapshot(); - }); - - test('textAlignVertical', () => { - const initial = { - textAlignVertical: 'center' - }; - - const expected = { - verticalAlign: 'middle' - }; - - expect(expandStyle(initial)).toEqual(expected); - }); - - test('flex', () => { - const value = 10; - - const initial = { - flex: value - }; - - const expected = { - flexGrow: value, - flexShrink: 1, - flexBasis: 'auto' - }; - - expect(expandStyle(initial)).toEqual(expected); - }); - - test('flex', () => { - expect(expandStyle({ display: 'flex' })).toEqual({ - display: 'flex', - flexShrink: 0 - }); - - expect(expandStyle({ display: 'flex', flex: 1 })).toEqual({ - display: 'flex', - flexGrow: 1, - flexShrink: 1, - flexBasis: 'auto' - }); - - expect(expandStyle({ display: 'flex', flex: 10 })).toEqual({ - display: 'flex', - flexGrow: 10, - flexShrink: 1, - flexBasis: 'auto' - }); - - expect(expandStyle({ display: 'flex', flexShrink: 1 })).toEqual({ - display: 'flex', - flexShrink: 1 - }); - - expect(expandStyle({ display: 'flex', flex: 1, flexShrink: 2 })).toEqual({ - display: 'flex', - flexGrow: 1, - flexShrink: 2, - flexBasis: 'auto' - }); - }); -}); diff --git a/src/apis/StyleSheet/__tests__/resolveBoxShadow-test.js b/src/apis/StyleSheet/__tests__/resolveBoxShadow-test.js deleted file mode 100644 index 460fdc47..00000000 --- a/src/apis/StyleSheet/__tests__/resolveBoxShadow-test.js +++ /dev/null @@ -1,56 +0,0 @@ -/* eslint-env jasmine, jest */ - -import resolveBoxShadow from '../resolveBoxShadow'; - -describe('apis/StyleSheet/resolveBoxShadow', () => { - test('shadowColor only', () => { - const resolvedStyle = {}; - const style = { shadowColor: 'red' }; - resolveBoxShadow(resolvedStyle, style); - - expect(resolvedStyle).toEqual({ - boxShadow: '0px 0px 0px red' - }); - }); - - test('shadowColor and shadowOpacity only', () => { - const resolvedStyle = {}; - const style = { shadowColor: 'red', shadowOpacity: 0.5 }; - resolveBoxShadow(resolvedStyle, style); - - expect(resolvedStyle).toEqual({ - boxShadow: '0px 0px 0px rgba(255,0,0,0.5)' - }); - }); - - test('shadowOffset only', () => { - const resolvedStyle = {}; - const style = { shadowOffset: { width: 1, height: 2 } }; - resolveBoxShadow(resolvedStyle, style); - - expect(resolvedStyle).toEqual({}); - }); - - test('shadowRadius only', () => { - const resolvedStyle = {}; - const style = { shadowRadius: 5 }; - resolveBoxShadow(resolvedStyle, style); - - expect(resolvedStyle).toEqual({}); - }); - - 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(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 deleted file mode 100644 index 56ef66cd..00000000 --- a/src/apis/StyleSheet/__tests__/resolveTextShadow-test.js +++ /dev/null @@ -1,19 +0,0 @@ -/* eslint-env jasmine, jest */ - -import resolveTextShadow from '../resolveTextShadow'; - -describe('apis/StyleSheet/resolveTextShadow', () => { - test('textShadowOffset', () => { - const resolvedStyle = {}; - const style = { - textShadowColor: 'red', - textShadowOffset: { width: 1, height: 2 }, - textShadowRadius: 5 - }; - resolveTextShadow(resolvedStyle, style); - - 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 deleted file mode 100644 index b5639092..00000000 --- a/src/apis/StyleSheet/__tests__/resolveTransform-test.js +++ /dev/null @@ -1,38 +0,0 @@ -/* eslint-env jasmine, jest */ - -import resolveTransform from '../resolveTransform'; - -describe('apis/StyleSheet/resolveTransform', () => { - // passthrough if transform value is ever a string - test('transform string', () => { - const resolvedStyle = {}; - const transform = 'perspective(50px) scaleX(20) translateX(20px) rotate(20deg)'; - - const style = { transform }; - resolveTransform(resolvedStyle, style); - - expect(resolvedStyle).toEqual({ transform }); - }); - - test('transform array', () => { - const resolvedStyle = {}; - const style = { - transform: [{ perspective: 50 }, { scaleX: 20 }, { translateX: 20 }, { rotate: '20deg' }] - }; - resolveTransform(resolvedStyle, style); - - expect(resolvedStyle).toEqual({ - transform: 'perspective(50px) scaleX(20) translateX(20px) rotate(20deg)' - }); - }); - - test('transformMatrix', () => { - const resolvedStyle = {}; - const style = { transformMatrix: [1, 2, 3, 4, 5, 6] }; - resolveTransform(resolvedStyle, style); - - 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 d9551404..903ab88f 100644 --- a/src/apis/StyleSheet/createReactDOMStyle.js +++ b/src/apis/StyleSheet/createReactDOMStyle.js @@ -1,7 +1,226 @@ -import expandStyle from './expandStyle'; -import i18nStyle from './i18nStyle'; +/** + * 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. + */ -const createReactDOMStyle = flattenedReactNativeStyle => - expandStyle(i18nStyle(flattenedReactNativeStyle)); +import normalizeValue from './normalizeValue'; +import processColor from '../../modules/processColor'; + +const emptyObject = {}; +const styleShortFormProperties = { + 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'], + overflow: ['overflowX', 'overflowY'], + padding: ['paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft'], + paddingHorizontal: ['paddingRight', 'paddingLeft'], + paddingVertical: ['paddingTop', 'paddingBottom'], + textDecorationLine: ['textDecoration'], + writingDirection: ['direction'] +}; + +const colorProps = { + backgroundColor: true, + borderColor: true, + borderTopColor: true, + borderRightColor: true, + borderBottomColor: true, + borderLeftColor: true, + color: true +}; + +const alphaSortProps = propsArray => + propsArray.sort((a, b) => { + if (a < b) { + return -1; + } + if (a > b) { + return 1; + } + return 0; + }); + +const defaultOffset = { height: 0, width: 0 }; + +/** + * Shadow + */ + +// TODO: add inset and spread support +const resolveShadow = (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); + const color = processColor(style.shadowColor, style.shadowOpacity); + + if (color) { + const boxShadow = `${offsetX} ${offsetY} ${blurRadius} ${color}`; + resolvedStyle.boxShadow = style.boxShadow ? `${style.boxShadow}, ${boxShadow}` : boxShadow; + } else if (style.boxShadow) { + resolvedStyle.boxShadow = style.boxShadow; + } +}; + +/** + * Text Shadow + */ + +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 = processColor(style.textShadowColor); + + if (color) { + resolvedStyle.textShadow = `${offsetX} ${offsetY} ${blurRadius} ${color}`; + } +}; + +/** + * Transform + */ + +// { scale: 2 } => 'scale(2)' +// { translateX: 20 } => 'translateX(20px)' +const mapTransform = transform => { + const type = Object.keys(transform)[0]; + const value = normalizeValue(type, transform[type]); + return `${type}(${value})`; +}; + +// [1,2,3,4,5,6] => 'matrix3d(1,2,3,4,5,6)' +const convertTransformMatrix = transformMatrix => { + const matrix = transformMatrix.join(','); + return `matrix3d(${matrix})`; +}; + +const resolveTransform = (resolvedStyle, style) => { + let transform = style.transform; + if (Array.isArray(style.transform)) { + transform = style.transform.map(mapTransform).join(' '); + } else if (style.transformMatrix) { + transform = convertTransformMatrix(style.transformMatrix); + } + resolvedStyle.transform = transform; +}; + +/** + * Reducer + */ + +const createReducer = (style, styleProps) => { + let hasResolvedShadow = false; + let hasResolvedTextShadow = false; + + return (resolvedStyle, prop) => { + const value = normalizeValue(prop, style[prop]); + if (value == null) { + return resolvedStyle; + } + + switch (prop) { + case 'display': { + resolvedStyle.display = value; + // default of 'flexShrink:0' has lowest precedence + if (style.display === 'flex' && style.flex == null && style.flexShrink == null) { + resolvedStyle.flexShrink = 0; + } + break; + } + // ignore React Native styles + case 'aspectRatio': + case 'elevation': + case 'overlayColor': + case 'resizeMode': + case 'tintColor': { + break; + } + case 'flex': { + resolvedStyle.flexGrow = value; + resolvedStyle.flexShrink = 1; + resolvedStyle.flexBasis = 'auto'; + break; + } + case 'shadowColor': + case 'shadowOffset': + case 'shadowOpacity': + case 'shadowRadius': { + if (!hasResolvedShadow) { + resolveShadow(resolvedStyle, style); + } + hasResolvedShadow = 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': + case 'transformMatrix': { + resolveTransform(resolvedStyle, style); + break; + } + default: { + // normalize color values + let finalValue = value; + if (colorProps[prop]) { + finalValue = processColor(value); + } + + 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] = finalValue; + } + }); + } else { + resolvedStyle[prop] = finalValue; + } + } + } + + return resolvedStyle; + }; +}; + +const createReactDOMStyle = style => { + if (!style) { + return emptyObject; + } + const styleProps = Object.keys(style); + const sortedStyleProps = alphaSortProps(styleProps); + const reducer = createReducer(style, styleProps); + const resolvedStyle = sortedStyleProps.reduce(reducer, {}); + return resolvedStyle; +}; module.exports = createReactDOMStyle; diff --git a/src/apis/StyleSheet/expandStyle.js b/src/apis/StyleSheet/expandStyle.js deleted file mode 100644 index dca07be7..00000000 --- a/src/apis/StyleSheet/expandStyle.js +++ /dev/null @@ -1,158 +0,0 @@ -/** - * 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 processColor from '../../modules/processColor'; -import resolveBoxShadow from './resolveBoxShadow'; -import resolveTextShadow from './resolveTextShadow'; -import resolveTransform from './resolveTransform'; - -const emptyObject = {}; -const styleShortFormProperties = { - 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'], - overflow: ['overflowX', 'overflowY'], - padding: ['paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft'], - paddingHorizontal: ['paddingRight', 'paddingLeft'], - paddingVertical: ['paddingTop', 'paddingBottom'], - textDecorationLine: ['textDecoration'], - writingDirection: ['direction'] -}; - -const colorProps = { - backgroundColor: true, - borderColor: true, - borderTopColor: true, - borderRightColor: true, - borderBottomColor: true, - borderLeftColor: true, - color: true -}; - -const alphaSortProps = propsArray => - propsArray.sort((a, b) => { - if (a < b) { - return -1; - } - if (a > b) { - return 1; - } - return 0; - }); - -const createReducer = (style, styleProps) => { - let hasResolvedBoxShadow = false; - let hasResolvedTextShadow = false; - - return (resolvedStyle, prop) => { - const value = normalizeValue(prop, style[prop]); - if (value == null) { - return resolvedStyle; - } - - switch (prop) { - case 'display': { - resolvedStyle.display = value; - // default of 'flexShrink:0' has lowest precedence - if (style.display === 'flex' && style.flex == null && style.flexShrink == null) { - resolvedStyle.flexShrink = 0; - } - break; - } - // ignore React Native styles - case 'aspectRatio': - case 'elevation': - case 'overlayColor': - case 'resizeMode': - case 'tintColor': { - 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); - } - 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: { - // normalize color values - let finalValue = value; - if (colorProps[prop]) { - finalValue = processColor(value); - } - - 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] = finalValue; - } - }); - } else { - resolvedStyle[prop] = finalValue; - } - } - } - - return resolvedStyle; - }; -}; - -const expandStyle = style => { - if (!style) { - return emptyObject; - } - const styleProps = Object.keys(style); - const sortedStyleProps = alphaSortProps(styleProps); - const reducer = createReducer(style, styleProps); - const resolvedStyle = sortedStyleProps.reduce(reducer, {}); - return resolvedStyle; -}; - -module.exports = expandStyle; diff --git a/src/apis/StyleSheet/resolveBoxShadow.js b/src/apis/StyleSheet/resolveBoxShadow.js deleted file mode 100644 index a524a7f4..00000000 --- a/src/apis/StyleSheet/resolveBoxShadow.js +++ /dev/null @@ -1,22 +0,0 @@ -import normalizeValue from './normalizeValue'; -import processColor from '../../modules/processColor'; - -const defaultOffset = { height: 0, width: 0 }; - -// TODO: add inset and spread support -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); - const color = processColor(style.shadowColor, style.shadowOpacity); - - if (color) { - const boxShadow = `${offsetX} ${offsetY} ${blurRadius} ${color}`; - resolvedStyle.boxShadow = style.boxShadow ? `${style.boxShadow}, ${boxShadow}` : boxShadow; - } else if (style.boxShadow) { - resolvedStyle.boxShadow = style.boxShadow; - } -}; - -module.exports = resolveBoxShadow; diff --git a/src/apis/StyleSheet/resolveTextShadow.js b/src/apis/StyleSheet/resolveTextShadow.js deleted file mode 100644 index 86f3b433..00000000 --- a/src/apis/StyleSheet/resolveTextShadow.js +++ /dev/null @@ -1,18 +0,0 @@ -import normalizeValue from './normalizeValue'; -import processColor from '../../modules/processColor'; - -const defaultOffset = { height: 0, width: 0 }; - -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 = processColor(style.textShadowColor); - - if (color) { - resolvedStyle.textShadow = `${offsetX} ${offsetY} ${blurRadius} ${color}`; - } -}; - -module.exports = resolveTextShadow; diff --git a/src/apis/StyleSheet/resolveTransform.js b/src/apis/StyleSheet/resolveTransform.js deleted file mode 100644 index 0e3ad203..00000000 --- a/src/apis/StyleSheet/resolveTransform.js +++ /dev/null @@ -1,27 +0,0 @@ -import normalizeValue from './normalizeValue'; - -// { scale: 2 } => 'scale(2)' -// { translateX: 20 } => 'translateX(20px)' -const mapTransform = transform => { - const type = Object.keys(transform)[0]; - const value = normalizeValue(type, transform[type]); - return `${type}(${value})`; -}; - -// [1,2,3,4,5,6] => 'matrix3d(1,2,3,4,5,6)' -const convertTransformMatrix = transformMatrix => { - const matrix = transformMatrix.join(','); - return `matrix3d(${matrix})`; -}; - -const resolveTransform = (resolvedStyle, style) => { - let transform = style.transform; - if (Array.isArray(style.transform)) { - transform = style.transform.map(mapTransform).join(' '); - } else if (style.transformMatrix) { - transform = convertTransformMatrix(style.transformMatrix); - } - resolvedStyle.transform = transform; -}; - -module.exports = resolveTransform;