diff --git a/packages/react-native-web/src/exports/StyleSheet/__tests__/compiler-createReactDOMStyle-test.js b/packages/react-native-web/src/exports/StyleSheet/__tests__/compiler-createReactDOMStyle-test.js index 5d24610ce..47f7c97c5 100644 --- a/packages/react-native-web/src/exports/StyleSheet/__tests__/compiler-createReactDOMStyle-test.js +++ b/packages/react-native-web/src/exports/StyleSheet/__tests__/compiler-createReactDOMStyle-test.js @@ -72,12 +72,6 @@ describe('compiler/createReactDOMStyle', () => { `); }); - test('aspectRatio', () => { - expect(createReactDOMStyle({ aspectRatio: 9 / 16 })).toEqual({ - aspectRatio: '0.5625' - }); - }); - describe('flexbox styles', () => { test('flex: -1', () => { expect(createReactDOMStyle({ flex: -1 })).toEqual({ @@ -190,70 +184,4 @@ describe('compiler/createReactDOMStyle', () => { `); }); }); - - test('fontVariant', () => { - expect( - createReactDOMStyle({ fontVariant: 'common-ligatures small-caps' }) - ).toEqual({ - fontVariant: 'common-ligatures small-caps' - }); - - expect( - createReactDOMStyle({ fontVariant: ['common-ligatures', 'small-caps'] }) - ).toEqual({ - fontVariant: 'common-ligatures small-caps' - }); - }); - - test('textAlignVertical', () => { - expect( - createReactDOMStyle({ - textAlignVertical: 'center' - }) - ).toEqual({ - verticalAlign: 'middle' - }); - }); - - test('verticalAlign', () => { - expect( - createReactDOMStyle({ - verticalAlign: 'top', - textAlignVertical: 'center' - }) - ).toEqual({ - verticalAlign: 'top' - }); - }); - - 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' }, - { matrix: [1, 2, 3, 4, 5, 6] }, - { matrix3d: [1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4] } - ] - }; - const resolved = createReactDOMStyle(style); - - expect(resolved).toEqual({ - transform: - 'perspective(50px) scaleX(20) translateX(20px) rotate(20deg) matrix(1,2,3,4,5,6) matrix3d(1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4)' - }); - }); - }); }); diff --git a/packages/react-native-web/src/exports/StyleSheet/__tests__/compiler-test.js b/packages/react-native-web/src/exports/StyleSheet/__tests__/compiler-test.js index 525ff47ae..5fb7397cf 100644 --- a/packages/react-native-web/src/exports/StyleSheet/__tests__/compiler-test.js +++ b/packages/react-native-web/src/exports/StyleSheet/__tests__/compiler-test.js @@ -11,7 +11,7 @@ describe('StyleSheet/compile', () => { describe('atomic', () => { test('converts style to atomic CSS', () => { const result = atomic({ - animationDirection: ['alternate', 'alternate-reverse'], + animationDirection: 'alternate,alternate-reverse', animationKeyframes: [ { '0%': { top: 0 }, '50%': { top: 5 }, '100%': { top: 10 } }, { from: { left: 0 }, to: { left: 10 } } @@ -34,7 +34,7 @@ describe('StyleSheet/compile', () => { { "$$css": true, "$$css$localize": true, - "animationDirection": "r-animationDirection-1kmv48j", + "animationDirection": "r-animationDirection-1wgwto7", "animationKeyframes": "r-animationKeyframes-zacbmr", "fontFamily": "r-fontFamily-1qd0xha", "insetInlineStart": [ @@ -63,7 +63,7 @@ describe('StyleSheet/compile', () => { [ [ [ - ".r-animationDirection-1kmv48j{animation-direction:alternate,alternate-reverse;}", + ".r-animationDirection-1wgwto7{animation-direction:alternate,alternate-reverse;}", ], 3, ], diff --git a/packages/react-native-web/src/exports/StyleSheet/__tests__/preprocess-test.js b/packages/react-native-web/src/exports/StyleSheet/__tests__/preprocess-test.js index 5e6803d18..9b03a7772 100644 --- a/packages/react-native-web/src/exports/StyleSheet/__tests__/preprocess-test.js +++ b/packages/react-native-web/src/exports/StyleSheet/__tests__/preprocess-test.js @@ -75,6 +75,76 @@ describe('StyleSheet/preprocess', () => { paddingInlineStart: 2 }); }); + + test('converts non-standard textAlignVertical', () => { + expect( + preprocess({ + textAlignVertical: 'center' + }) + ).toEqual({ + verticalAlign: 'middle' + }); + + expect( + preprocess({ + verticalAlign: 'top', + textAlignVertical: 'center' + }) + ).toEqual({ + verticalAlign: 'top' + }); + }); + + test('aspectRatio', () => { + expect(preprocess({ aspectRatio: 9 / 16 })).toEqual({ + aspectRatio: '0.5625' + }); + }); + + test('fontVariant', () => { + expect( + preprocess({ fontVariant: 'common-ligatures small-caps' }) + ).toEqual({ + fontVariant: 'common-ligatures small-caps' + }); + + expect( + preprocess({ fontVariant: ['common-ligatures', 'small-caps'] }) + ).toEqual({ + fontVariant: 'common-ligatures small-caps' + }); + }); + + 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 = preprocess(style); + + expect(resolved).toEqual({ transform }); + }); + + test('array', () => { + const style = { + transform: [ + { perspective: 50 }, + { scaleX: 20 }, + { translateX: 20 }, + { rotate: '20deg' }, + { matrix: [1, 2, 3, 4, 5, 6] }, + { matrix3d: [1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4] } + ] + }; + const resolved = preprocess(style); + + expect(resolved).toEqual({ + transform: + 'perspective(50px) scaleX(20) translateX(20px) rotate(20deg) matrix(1,2,3,4,5,6) matrix3d(1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4)' + }); + }); + }); }); describe('preprocesses multiple shadow styles into a single declaration', () => { diff --git a/packages/react-native-web/src/exports/StyleSheet/compiler/createReactDOMStyle.js b/packages/react-native-web/src/exports/StyleSheet/compiler/createReactDOMStyle.js index e28f380ca..627452751 100644 --- a/packages/react-native-web/src/exports/StyleSheet/compiler/createReactDOMStyle.js +++ b/packages/react-native-web/src/exports/StyleSheet/compiler/createReactDOMStyle.js @@ -9,7 +9,6 @@ import normalizeValueWithProperty from './normalizeValueWithProperty'; import canUseDOM from '../../../modules/canUseDom'; -import { warnOnce } from '../../../modules/warnOnce'; type Style = { [key: string]: any }; @@ -33,13 +32,6 @@ const supportsCSS3TextDecoration = (window.CSS.supports('text-decoration-line', 'none') || window.CSS.supports('-webkit-text-decoration-line', 'none'))); -const ignoredProps = { - elevation: true, - overlayColor: true, - resizeMode: true, - tintColor: true -}; - const MONOSPACE_FONT_STACK = 'monospace,monospace'; const SYSTEM_FONT_STACK = @@ -114,36 +106,6 @@ const STYLE_SHORT_FORM_EXPANSIONS = { //paddingInlineEnd: ['marginRight'], }; -/** - * Transform - */ - -// { scale: 2 } => 'scale(2)' -// { translateX: 20 } => 'translateX(20px)' -// { matrix: [1,2,3,4,5,6] } => 'matrix(1,2,3,4,5,6)' -const mapTransform = (transform: Object): string => { - const type = Object.keys(transform)[0]; - const value = transform[type]; - if (type === 'matrix' || type === 'matrix3d') { - return `${type}(${value.join(',')})`; - } else { - const normalizedValue = normalizeValueWithProperty(value, type); - return `${type}(${normalizedValue})`; - } -}; - -export const createTransformValue = (style: Style): string => { - let transform = style.transform; - if (Array.isArray(style.transform)) { - warnOnce( - 'transform', - '"transform" style array value is deprecated. Use space-separated string functions, e.g., "scaleX(2) rotateX(15deg)".' - ); - transform = style.transform.map(mapTransform).join(' '); - } - return transform; -}; - /** * Reducer */ @@ -160,16 +122,12 @@ const createReactDOMStyle = (style: Style, isInline?: boolean): Style => { if ( // Ignore everything with a null value - value == null || - // Ignore some React Native styles - ignoredProps[prop] + value == null ) { continue; } - if (prop === 'aspectRatio') { - resolvedStyle[prop] = value.toString(); - } else if (prop === 'backgroundClip') { + if (prop === 'backgroundClip') { // TODO: remove once this issue is fixed // https://github.com/rofrischmann/inline-style-prefixer/issues/159 if (value === 'text') { @@ -196,24 +154,6 @@ const createReactDOMStyle = (style: Style, isInline?: boolean): Style => { } else { resolvedStyle[prop] = value; } - } else if (prop === 'fontVariant') { - if (Array.isArray(value) && value.length > 0) { - warnOnce( - 'fontVariant', - '"fontVariant" style array value is deprecated. Use space-separated values.' - ); - resolvedStyle.fontVariant = value.join(' '); - } else { - resolvedStyle[prop] = value; - } - } else if (prop === 'textAlignVertical') { - warnOnce( - 'textAlignVertical', - '"textAlignVertical" style is deprecated. Use "verticalAlign".' - ); - if (resolvedStyle.verticalAlign == null) { - resolvedStyle.verticalAlign = value === 'center' ? 'middle' : value; - } } else if (prop === 'textDecorationLine') { // use 'text-decoration' for browsers that only support CSS2 // text-decoration (e.g., IE, Edge) @@ -222,8 +162,6 @@ const createReactDOMStyle = (style: Style, isInline?: boolean): Style => { } else { resolvedStyle.textDecorationLine = value; } - } else if (prop === 'transform' || prop === 'transformMatrix') { - resolvedStyle.transform = createTransformValue(style); } else if (prop === 'writingDirection') { resolvedStyle.direction = value; } else { @@ -265,7 +203,7 @@ const createReactDOMStyle = (style: Style, isInline?: boolean): Style => { } }); } else { - resolvedStyle[prop] = Array.isArray(value) ? value.join(',') : value; + resolvedStyle[prop] = value; } } } diff --git a/packages/react-native-web/src/exports/StyleSheet/preprocess.js b/packages/react-native-web/src/exports/StyleSheet/preprocess.js index 2a57070a4..3ff077f98 100644 --- a/packages/react-native-web/src/exports/StyleSheet/preprocess.js +++ b/packages/react-native-web/src/exports/StyleSheet/preprocess.js @@ -56,6 +56,23 @@ export const createTextShadowValue = (style: Object): void | string => { } }; +// { scale: 2 } => 'scale(2)' +// { translateX: 20 } => 'translateX(20px)' +// { matrix: [1,2,3,4,5,6] } => 'matrix(1,2,3,4,5,6)' +const mapTransform = (transform: Object): string => { + const type = Object.keys(transform)[0]; + const value = transform[type]; + if (type === 'matrix' || type === 'matrix3d') { + return `${type}(${value.join(',')})`; + } else { + const normalizedValue = normalizeValueWithProperty(value, type); + return `${type}(${normalizedValue})`; + } +}; +export const createTransformValue = (value: Array): string => { + return value.map(mapTransform).join(' '); +}; + const PROPERTIES_STANDARD: { [key: string]: string } = { borderBottomEndRadius: 'borderEndEndRadius', borderBottomStartRadius: 'borderEndStartRadius', @@ -79,6 +96,13 @@ const PROPERTIES_STANDARD: { [key: string]: string } = { start: 'insetInlineStart' }; +const ignoredProps = { + elevation: true, + overlayColor: true, + resizeMode: true, + tintColor: true +}; + /** * Preprocess styles */ @@ -88,9 +112,64 @@ export const preprocess = ( const style = originalStyle || emptyObject; const nextStyle = {}; + // Convert shadow styles + if ( + style.shadowColor != null || + style.shadowOffset != null || + style.shadowOpacity != null || + style.shadowRadius != null + ) { + warnOnce( + 'shadowStyles', + `"shadow*" style props are deprecated. Use "boxShadow".` + ); + const boxShadowValue = createBoxShadowValue(style); + if (boxShadowValue != null && nextStyle.boxShadow == null) { + const { boxShadow } = style; + const value = boxShadow + ? `${boxShadow}, ${boxShadowValue}` + : boxShadowValue; + nextStyle.boxShadow = value; + } + } + + // Convert text shadow styles + if ( + style.textShadowColor != null || + style.textShadowOffset != null || + style.textShadowRadius != null + ) { + warnOnce( + 'textShadowStyles', + `"textShadow*" style props are deprecated. Use "textShadow".` + ); + const textShadowValue = createTextShadowValue(style); + if (textShadowValue != null && nextStyle.textShadow == null) { + const { textShadow } = style; + const value = textShadow + ? `${textShadow}, ${textShadowValue}` + : textShadowValue; + nextStyle.textShadow = value; + } + } + for (const originalProp in style) { + if ( + // Ignore some React Native styles + ignoredProps[originalProp] != null || + originalProp === 'shadowColor' || + originalProp === 'shadowOffset' || + originalProp === 'shadowOpacity' || + originalProp === 'shadowRadius' || + originalProp === 'textShadowColor' || + originalProp === 'textShadowOffset' || + originalProp === 'textShadowRadius' + ) { + continue; + } + const originalValue = style[originalProp]; - let prop = PROPERTIES_STANDARD[originalProp] || originalProp; + const prop = PROPERTIES_STANDARD[originalProp] || originalProp; let value = originalValue; if ( @@ -100,42 +179,37 @@ export const preprocess = ( continue; } - // Convert shadow styles - if ( - prop === 'shadowColor' || - prop === 'shadowOffset' || - prop === 'shadowOpacity' || - prop === 'shadowRadius' - ) { - const boxShadowValue = createBoxShadowValue(style); - if (boxShadowValue != null && nextStyle.boxShadow == null) { - const { boxShadow } = style; - prop = 'boxShadow'; - value = boxShadow ? `${boxShadow}, ${boxShadowValue}` : boxShadowValue; - } else { - continue; + if (prop === 'aspectRatio') { + nextStyle[prop] = value.toString(); + } else if (prop === 'fontVariant') { + if (Array.isArray(value) && value.length > 0) { + warnOnce( + 'fontVariant', + '"fontVariant" style array value is deprecated. Use space-separated values.' + ); + value = value.join(' '); } - } - - // Convert text shadow styles - if ( - prop === 'textShadowColor' || - prop === 'textShadowOffset' || - prop === 'textShadowRadius' - ) { - const textShadowValue = createTextShadowValue(style); - if (textShadowValue != null && nextStyle.textShadow == null) { - const { textShadow } = style; - prop = 'textShadow'; - value = textShadow - ? `${textShadow}, ${textShadowValue}` - : textShadowValue; - } else { - continue; + nextStyle[prop] = value; + } else if (prop === 'textAlignVertical') { + warnOnce( + 'textAlignVertical', + '"textAlignVertical" style is deprecated. Use "verticalAlign".' + ); + if (style.verticalAlign == null) { + nextStyle.verticalAlign = value === 'center' ? 'middle' : value; + } + } else if (prop === 'transform') { + if (Array.isArray(value)) { + warnOnce( + 'transform', + '"transform" style array value is deprecated. Use space-separated string functions, e.g., "scaleX(2) rotateX(15deg)".' + ); + value = createTransformValue(value); } + nextStyle.transform = value; + } else { + nextStyle[prop] = value; } - - nextStyle[prop] = value; } // $FlowIgnore diff --git a/packages/react-native-web/src/exports/StyleSheet/validate.js b/packages/react-native-web/src/exports/StyleSheet/validate.js index 1bdbf3625..16ca7395e 100644 --- a/packages/react-native-web/src/exports/StyleSheet/validate.js +++ b/packages/react-native-web/src/exports/StyleSheet/validate.js @@ -68,15 +68,10 @@ export function validate(obj: Object) { let suggestion = ''; if (prop === 'animation' || prop === 'animationName') { suggestion = 'Did you mean "animationKeyframes"?'; - // } else if (prop === 'boxShadow') { - // suggestion = 'Did you mean "shadow{Color,Offset,Opacity,Radius}"?'; isInvalid = true; } else if (prop === 'direction') { suggestion = 'Did you mean "writingDirection"?'; isInvalid = true; - } else if (prop === 'verticalAlign') { - suggestion = 'Did you mean "textAlignVertical"?'; - isInvalid = true; } else if (invalidShortforms[prop]) { suggestion = 'Please use long-form properties.'; isInvalid = true;