diff --git a/packages/react-native/Libraries/StyleSheet/__tests__/processBackgroundImage-itest.js b/packages/react-native/Libraries/StyleSheet/__tests__/processBackgroundImage-itest.js index 08a0ca0b970..5dc05646b27 100644 --- a/packages/react-native/Libraries/StyleSheet/__tests__/processBackgroundImage-itest.js +++ b/packages/react-native/Libraries/StyleSheet/__tests__/processBackgroundImage-itest.js @@ -986,6 +986,23 @@ describe('processBackgroundImage', () => { expect(result[0].shape).toEqual('ellipse'); }); + it('should reject a circle with a percentage radius', () => { + const input = 'radial-gradient(circle 50%, red, blue)'; + expect(processBackgroundImage(input)).toEqual([]); + }); + + it('should reject an inferred circle with a percentage radius', () => { + const input = 'radial-gradient(50%, red, blue)'; + expect(processBackgroundImage(input)).toEqual([]); + }); + + it('should allow percentage sizes for ellipses', () => { + const input = 'radial-gradient(50% 20%, red, blue)'; + const result = processBackgroundImage(input); + expect(result[0].shape).toEqual('ellipse'); + expect(result[0].size).toEqual({x: '50%', y: '20%'}); + }); + it('should handle radial gradient with explicit shape with size', () => { const input = 'radial-gradient(circle 100px at center, red, blue 80%)'; const result = processBackgroundImage(input); @@ -999,6 +1016,22 @@ describe('processBackgroundImage', () => { expect(result[0].type).toEqual('radial-gradient'); }); + it('should handle radial gradient with explicit size and position', () => { + const input = 'radial-gradient(circle 100px at 25% 75%, red, blue)'; + const result = processBackgroundImage(input); + expect(result[0].shape).toEqual('circle'); + expect(result[0].size).toEqual({x: 100, y: 100}); + expect(result[0].position).toEqual({left: '25%', top: '75%'}); + }); + + it('should handle radial gradient with two explicit sizes and position', () => { + const input = 'radial-gradient(50px 100px at left bottom, red, blue)'; + const result = processBackgroundImage(input); + expect(result[0].shape).toEqual('ellipse'); + expect(result[0].size).toEqual({x: 50, y: 100}); + expect(result[0].position).toEqual({left: '0%', top: '100%'}); + }); + // 1. position syntax: [ left | center | right | top | bottom | ] it('should handle radial gradient position length syntax', () => { const input = 'radial-gradient(circle at 20px, red, blue)'; diff --git a/packages/react-native/Libraries/StyleSheet/processBackgroundImage.js b/packages/react-native/Libraries/StyleSheet/processBackgroundImage.js index f733c96c964..23c0b20cc52 100644 --- a/packages/react-native/Libraries/StyleSheet/processBackgroundImage.js +++ b/packages/react-native/Libraries/StyleSheet/processBackgroundImage.js @@ -348,6 +348,11 @@ function parseRadialGradientCSSString( size = {x: sizeX, y: sizeY}; } else { hasExplicitSingleSize = true; + // The token after the size is not a second size value (e.g. 'at' or a + // shape keyword). Put it back so the loop can process it, otherwise + // the position would be silently dropped and its values re-parsed as + // a new size. + firstPartTokens.unshift(token); } } else if (tokenTrimmed === 'at') { let top: string | number; @@ -562,6 +567,15 @@ function parseRadialGradientCSSString( // If a single size is explicitly set and the shape is an ellipse, return null and do not apply any gradient. Same as web. return null; } + + if ( + shape === 'circle' && + typeof size === 'object' && + (typeof size.x === 'string' || typeof size.y === 'string') + ) { + // A circle radius must be a . Percentages are only valid for ellipses, so return null and do not apply any gradient. Same as web. + return null; + } } const colorStops = parseColorStopsCSSString(remainingParts);