Skip to content

Commit 4f89321

Browse files
improve radial gradient parsing
1 parent 729fbd1 commit 4f89321

File tree

3 files changed

+526
-39
lines changed

3 files changed

+526
-39
lines changed

packages/react-native/Libraries/StyleSheet/StyleSheetTypes.js

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -738,6 +738,23 @@ type RadialExtent =
738738
| 'closest-side'
739739
| 'farthest-corner'
740740
| 'farthest-side';
741+
type RadialGradientPosition =
742+
| {
743+
top: number | string,
744+
left: number | string,
745+
}
746+
| {
747+
top: number | string,
748+
right: number | string,
749+
}
750+
| {
751+
bottom: number | string,
752+
left: number | string,
753+
}
754+
| {
755+
bottom: number | string,
756+
right: number | string,
757+
};
741758
type RadialGradientValue = {
742759
type: 'radialGradient',
743760
shape: 'circle' | 'ellipse',
@@ -747,11 +764,7 @@ type RadialGradientValue = {
747764
x: number | string,
748765
y: number | string,
749766
},
750-
position: {
751-
x: number | string,
752-
y: number | string,
753-
},
754-
direction?: string,
767+
position: RadialGradientPosition,
755768
colorStops: $ReadOnlyArray<{
756769
color: ____ColorValue_Internal,
757770
positions?: $ReadOnlyArray<string>,

packages/react-native/Libraries/StyleSheet/__tests__/processBackgroundImage-test.js

Lines changed: 187 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -901,7 +901,7 @@ describe('processBackgroundImage', () => {
901901
{color: 'red', positions: ['10%', 20]},
902902
{color: 'blue', positions: ['30%', 40]},
903903
],
904-
position: {x: '50%', y: 50},
904+
position: {top: '50%', left: '50%'},
905905
size: 'closest-side',
906906
},
907907
{
@@ -911,7 +911,7 @@ describe('processBackgroundImage', () => {
911911
{color: 'red', positions: ['10%', 20]},
912912
{color: 'blue', positions: ['30%', 40]},
913913
],
914-
position: {x: '50%', y: 50},
914+
position: {top: '50%', left: 50},
915915
size: {
916916
x: 100,
917917
y: 100,
@@ -925,7 +925,7 @@ describe('processBackgroundImage', () => {
925925
{color: processColor('blue'), position: '30%'},
926926
{color: processColor('blue'), position: 40},
927927
]);
928-
expect(result[0].position).toEqual({x: '50%', y: 50});
928+
expect(result[0].position).toEqual({top: '50%', left: '50%'});
929929
expect(result[0].size).toEqual('closest-side');
930930
expect(result[0].shape).toEqual('circle');
931931
expect(result[0].type).toEqual('radialGradient');
@@ -936,24 +936,197 @@ describe('processBackgroundImage', () => {
936936
});
937937
});
938938

939-
it('should process default values for radial gradient object syntax', () => {
940-
const input = [
941-
{
942-
type: 'radialGradient',
943-
colorStops: [
944-
{color: 'red', positions: null},
945-
{color: 'blue', positions: null},
946-
],
947-
},
948-
];
939+
it('should process radial gradient without shape, size and position', () => {
940+
const input = 'radial-gradient(red, blue)';
949941
const result = processBackgroundImage(input);
950942
expect(result[0].colorStops).toEqual([
951943
{color: processColor('red'), position: null},
952944
{color: processColor('blue'), position: null},
953945
]);
954-
expect(result[0].position).toEqual({x: '50%', y: '50%'});
946+
expect(result[0].position).toEqual({top: '50%', left: '50%'});
955947
expect(result[0].size).toEqual('farthest-corner');
956948
expect(result[0].shape).toEqual('ellipse');
957949
expect(result[0].type).toEqual('radialGradient');
958950
});
951+
952+
it('should process radial gradient with shape circle', () => {
953+
const input = 'radial-gradient(circle, red, blue)';
954+
const result = processBackgroundImage(input);
955+
expect(result[0].colorStops).toEqual([
956+
{color: processColor('red'), position: null},
957+
{color: processColor('blue'), position: null},
958+
]);
959+
expect(result[0].position).toEqual({top: '50%', left: '50%'});
960+
expect(result[0].size).toEqual('farthest-corner');
961+
expect(result[0].shape).toEqual('circle');
962+
expect(result[0].type).toEqual('radialGradient');
963+
});
964+
965+
it('should infer radial gradient circle shape from single length', () => {
966+
const input = 'radial-gradient(100px, red, blue)';
967+
const result = processBackgroundImage(input);
968+
expect(result[0].size).toEqual({x: 100, y: 100});
969+
expect(result[0].shape).toEqual('circle');
970+
});
971+
972+
it('should infer radial gradient ellipse shape from double length', () => {
973+
const input = 'radial-gradient(100px 50px, red, blue)';
974+
const result = processBackgroundImage(input);
975+
expect(result[0].size).toEqual({x: 100, y: 50});
976+
expect(result[0].shape).toEqual('ellipse');
977+
});
978+
979+
it('should handle radial gradient with explicit shape with size', () => {
980+
const input = 'radial-gradient(circle 100px at center, red, blue 80%)';
981+
const result = processBackgroundImage(input);
982+
expect(result[0].colorStops).toEqual([
983+
{color: processColor('red'), position: null},
984+
{color: processColor('blue'), position: '80%'},
985+
]);
986+
expect(result[0].position).toEqual({top: '50%', left: '50%'});
987+
expect(result[0].size).toEqual({x: 100, y: 100});
988+
expect(result[0].shape).toEqual('circle');
989+
expect(result[0].type).toEqual('radialGradient');
990+
});
991+
992+
// 1. position syntax: [ left | center | right | top | bottom | <length-percentage> ]
993+
it('should handle radial gradient position length syntax', () => {
994+
const input = 'radial-gradient(circle at 20px, red, blue)';
995+
const result = processBackgroundImage(input);
996+
expect(result[0].position).toEqual({left: 20, top: '50%'});
997+
});
998+
999+
// 2. position syntax: [ left | center | right ] && [ top | center | bottom ]
1000+
it('should handle radial gradient position keywords syntax', () => {
1001+
const input = 'radial-gradient(circle at left top, red, blue)';
1002+
const input1 = 'radial-gradient(circle at top left, red, blue)';
1003+
const result = processBackgroundImage(input);
1004+
const result1 = processBackgroundImage(input1);
1005+
expect(result[0].position).toEqual({left: '0%', top: '0%'});
1006+
expect(result1[0].position).toEqual({left: '0%', top: '0%'});
1007+
});
1008+
1009+
// 3. position syntax: [ left | center | right | <length-percentage> ] [ top | center | bottom | <length-percentage> ]
1010+
it('should handle left position top position syntax', () => {
1011+
const input = 'radial-gradient(circle at left 20px, red, blue)';
1012+
const input1 = 'radial-gradient(circle at 20px 20px, red, blue)';
1013+
const input2 = 'radial-gradient(circle at right 50px, red, blue)';
1014+
const result = processBackgroundImage(input);
1015+
const result1 = processBackgroundImage(input1);
1016+
const result2 = processBackgroundImage(input2);
1017+
expect(result[0].position).toEqual({left: '0%', top: 20});
1018+
expect(result1[0].position).toEqual({left: 20, top: 20});
1019+
expect(result2[0].position).toEqual({left: '100%', top: 50});
1020+
});
1021+
1022+
// 4. position syntax: [ [ left | right ] <length-percentage> ] && [ [ top | bottom ] <length-percentage> ]
1023+
it('should handle left position top position syntax', () => {
1024+
const input = 'radial-gradient(at top 0% right 10%, red, blue)';
1025+
const result = processBackgroundImage(input);
1026+
expect(result[0].position).toEqual({right: '10%', top: '0%'});
1027+
});
1028+
1029+
it('should handle color stops with transition hints in radial gradients', () => {
1030+
const input =
1031+
'radial-gradient(circle, red 0%, 25%, blue 50%, 75%, green 100%)';
1032+
const result = processBackgroundImage(input);
1033+
expect(result[0].colorStops).toEqual([
1034+
{color: processColor('red'), position: '0%'},
1035+
{color: null, position: '25%'},
1036+
{color: processColor('blue'), position: '50%'},
1037+
{color: null, position: '75%'},
1038+
{color: processColor('green'), position: '100%'},
1039+
]);
1040+
expect(result[0].shape).toEqual('circle');
1041+
expect(result[0].type).toEqual('radialGradient');
1042+
});
1043+
1044+
it('should process multiple gradients with both radial and linear', () => {
1045+
const input = `
1046+
radial-gradient(circle at top left, red, blue),
1047+
linear-gradient(to bottom, green, yellow)
1048+
`;
1049+
const result = processBackgroundImage(input);
1050+
expect(result).toHaveLength(2);
1051+
expect(result[0].type).toEqual('radialGradient');
1052+
expect(result[0].shape).toEqual('circle');
1053+
expect(result[0].position).toEqual({top: '0%', left: '0%'});
1054+
expect(result[0].colorStops).toEqual([
1055+
{color: processColor('red'), position: null},
1056+
{color: processColor('blue'), position: null},
1057+
]);
1058+
expect(result[1].type).toEqual('linearGradient');
1059+
expect(result[1].direction).toEqual({
1060+
type: 'angle',
1061+
value: 180,
1062+
});
1063+
expect(result[1].colorStops).toEqual([
1064+
{color: processColor('green'), position: null},
1065+
{color: processColor('yellow'), position: null},
1066+
]);
1067+
});
1068+
1069+
it('should handle mixed case in radial gradient syntax', () => {
1070+
const input = 'RaDiAl-GrAdIeNt(CiRcLe ClOsEsT-sIdE aT cEnTeR, rEd, bLuE)';
1071+
const result = processBackgroundImage(input);
1072+
expect(result[0].colorStops).toEqual([
1073+
{color: processColor('red'), position: null},
1074+
{color: processColor('blue'), position: null},
1075+
]);
1076+
expect(result[0].position).toEqual({top: '50%', left: '50%'});
1077+
expect(result[0].size).toEqual('closest-side');
1078+
expect(result[0].shape).toEqual('circle');
1079+
expect(result[0].type).toEqual('radialGradient');
1080+
});
1081+
1082+
it('should handle whitespace variations in radial gradients', () => {
1083+
const input =
1084+
'radial-gradient( circle farthest-corner at 25% 75% , red 0% , blue 100% )';
1085+
const result = processBackgroundImage(input);
1086+
expect(result[0].colorStops).toEqual([
1087+
{color: processColor('red'), position: '0%'},
1088+
{color: processColor('blue'), position: '100%'},
1089+
]);
1090+
expect(result[0].position).toEqual({left: '25%', top: '75%'});
1091+
expect(result[0].size).toEqual('farthest-corner');
1092+
expect(result[0].shape).toEqual('circle');
1093+
expect(result[0].type).toEqual('radialGradient');
1094+
});
1095+
1096+
it('should handle position keywords in different orders', () => {
1097+
const input1 = 'radial-gradient(circle at top left, red, blue)';
1098+
const input2 = 'radial-gradient(circle at left top, red, blue)';
1099+
const result1 = processBackgroundImage(input1);
1100+
const result2 = processBackgroundImage(input2);
1101+
1102+
expect(result1[0].position).toEqual({top: '0%', left: '0%'});
1103+
expect(result2[0].position).toEqual({top: '0%', left: '0%'});
1104+
});
1105+
1106+
it('should handle invalid radial gradient syntax', () => {
1107+
const input = 'radial-gradient(circle at top leftt, red, blue, green)';
1108+
const input1 = 'radial-gradient(circle at, red, blue, green)';
1109+
const input2 = 'radial-gradient(ellipse 100px, red, blue, green)';
1110+
const input3 =
1111+
'radial-gradient(ellipse at top 20% top 50%, red, blue, green)';
1112+
const result = processBackgroundImage(input);
1113+
const result1 = processBackgroundImage(input1);
1114+
const result2 = processBackgroundImage(input2);
1115+
const result3 = processBackgroundImage(input3);
1116+
expect(result).toEqual([]);
1117+
expect(result1).toEqual([]);
1118+
expect(result2).toEqual([]);
1119+
expect(result3).toEqual([]);
1120+
});
1121+
1122+
it('should handle multiple color stops with radial gradient syntax', () => {
1123+
const input = 'radial-gradient(red 0%, yellow 30%, green 60%, blue 100%)';
1124+
const result = processBackgroundImage(input);
1125+
expect(result[0].colorStops).toEqual([
1126+
{color: processColor('red'), position: '0%'},
1127+
{color: processColor('yellow'), position: '30%'},
1128+
{color: processColor('green'), position: '60%'},
1129+
{color: processColor('blue'), position: '100%'},
1130+
]);
1131+
});
9591132
});

0 commit comments

Comments
 (0)