Skip to content

Commit 7ca3007

Browse files
transition hint css syntax support
1 parent 8dc2b51 commit 7ca3007

File tree

3 files changed

+84
-20
lines changed

3 files changed

+84
-20
lines changed

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,4 +715,16 @@ describe('processBackgroundImage', () => {
715715
{color: processColor('blue'), position: 1},
716716
]);
717717
});
718+
719+
it('should process color transition hint', () => {
720+
const input = 'linear-gradient(red, 40%, blue)';
721+
const result = processBackgroundImage(input);
722+
expect(result[0].type).toBe('linearGradient');
723+
expect(result[0].direction).toEqual({type: 'angle', value: 180});
724+
expect(result[0].colorStops).toEqual([
725+
{color: processColor('red'), position: 0},
726+
{color: null, position: 0.4},
727+
{color: processColor('blue'), position: 1},
728+
]);
729+
});
718730
});

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

Lines changed: 58 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -190,47 +190,85 @@ function parseCSSLinearGradient(
190190
// If first part is not an angle/direction or a color stop, return an empty array and do not apply any gradient. Same as web.
191191
return [];
192192
}
193-
colorStopRegex.lastIndex = 0;
194193

194+
const colorStopsString = parts.join(',');
195195
const colorStops = [];
196-
const fullColorStopsStr = parts.join(',');
197-
let colorStopMatch;
198-
while ((colorStopMatch = colorStopRegex.exec(fullColorStopsStr))) {
199-
const [, color, position1, position2] = colorStopMatch;
200-
const processedColor = processColor(color.trim().toLowerCase());
201-
if (processedColor == null) {
202-
// If a color is invalid, return an empty array and do not apply any gradient. Same as web.
196+
// split by comma, but not if it's inside a parentheses. e.g. red, rgba(0, 0, 0, 0.5), green => ["red", "rgba(0, 0, 0, 0.5)", "green"]
197+
const stops = colorStopsString.split(/,(?![^(]*\))/);
198+
for (const stop of stops) {
199+
const trimmedStop = stop.trim().toLowerCase();
200+
// Match function like pattern or single words
201+
const parts = trimmedStop.match(/\S+\([^)]*\)|\S+/g);
202+
if (parts == null) {
203+
// If a color stop is invalid, return an empty array and do not apply any gradient. Same as web.
203204
return [];
204205
}
205-
206-
if (typeof position1 !== 'undefined') {
207-
if (position1.endsWith('%')) {
206+
// Case 1: [color, position, position]
207+
if (parts.length === 3) {
208+
const color = parts[0];
209+
const position1 = parts[1];
210+
const position2 = parts[2];
211+
const processedColor = processColor(color);
212+
if (processedColor == null) {
213+
// If a color is invalid, return an empty array and do not apply any gradient. Same as web.
214+
return [];
215+
}
216+
if (position1.endsWith('%') && position2.endsWith('%')) {
208217
colorStops.push({
209218
color: processedColor,
210219
position: parseFloat(position1) / 100,
211220
});
221+
colorStops.push({
222+
color: processedColor,
223+
position: parseFloat(position2) / 100,
224+
});
212225
} else {
213226
// If a position is invalid, return an empty array and do not apply any gradient. Same as web.
214227
return [];
215228
}
216-
} else {
217-
colorStops.push({
218-
color: processedColor,
219-
position: null,
220-
});
221229
}
222-
223-
if (typeof position2 !== 'undefined') {
224-
if (position2.endsWith('%')) {
230+
// Case 2: [color, position]
231+
else if (parts.length === 2) {
232+
const color = parts[0];
233+
const position = parts[1];
234+
const processedColor = processColor(color);
235+
if (processedColor == null) {
236+
// If a color is invalid, return an empty array and do not apply any gradient. Same as web.
237+
return [];
238+
}
239+
if (position.endsWith('%')) {
225240
colorStops.push({
226241
color: processedColor,
227-
position: parseFloat(position2) / 100,
242+
position: parseFloat(position) / 100,
228243
});
229244
} else {
230245
// If a position is invalid, return an empty array and do not apply any gradient. Same as web.
231246
return [];
232247
}
233248
}
249+
// Case 3: [color]
250+
// Case 4: [position] => transition hint syntax
251+
else if (parts.length === 1) {
252+
if (parts[0].endsWith('%')) {
253+
colorStops.push({
254+
color: null,
255+
position: parseFloat(parts[0]) / 100,
256+
});
257+
} else {
258+
const processedColor = processColor(parts[0]);
259+
if (processedColor == null) {
260+
// If a color is invalid, return an empty array and do not apply any gradient. Same as web.
261+
return [];
262+
}
263+
colorStops.push({
264+
color: processedColor,
265+
position: null,
266+
});
267+
}
268+
} else {
269+
// If a color stop is invalid, return an empty array and do not apply any gradient. Same as web.
270+
return [];
271+
}
234272
}
235273

236274
const fixedColorStops = getFixedColorStops(colorStops);

packages/rn-tester/js/examples/LinearGradient/LinearGradientExample.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,4 +224,18 @@ exports.examples = [
224224
);
225225
},
226226
},
227+
{
228+
title: 'Transition hint',
229+
description: 'Linear gradient with transition hint',
230+
render(): React.Node {
231+
return (
232+
<GradientBox
233+
style={{
234+
experimental_backgroundImage: 'linear-gradient(red, 40%, blue)',
235+
}}
236+
testID="linear-gradient-transition-hint"
237+
/>
238+
);
239+
},
240+
},
227241
];

0 commit comments

Comments
 (0)