@@ -17,7 +17,8 @@ const processColor = require('./processColor').default;
1717// Linear Gradient
1818const LINEAR_GRADIENT_DIRECTION_REGEX =
1919 / ^ t o \s + (?: t o p | b o t t o m | l e f t | r i g h t ) (?: \s + (?: t o p | b o t t o m | l e f t | r i g h t ) ) ? / i;
20- const LINEAR_GRADIENT_ANGLE_UNIT_REGEX = / ^ ( [ + - ] ? \d * \. ? \d + ) ( d e g | g r a d | r a d | t u r n ) $ / i;
20+ const LINEAR_GRADIENT_ANGLE_UNIT_REGEX =
21+ / ^ ( [ + - ] ? \d * \. ? \d + ) ( d e g | g r a d | r a d | t u r n ) $ / i;
2122const LINEAR_GRADIENT_DEFAULT_DIRECTION : LinearGradientDirection = {
2223 type : 'angle' ,
2324 value : 180 ,
@@ -34,13 +35,16 @@ type LinearGradientBackgroundImage = {
3435 color : ColorStopColor ,
3536 position : ColorStopPosition ,
3637 } > ,
37- }
38+ } ;
3839
3940// Radial Gradient
4041const RADIAL_SHAPE_REGEX = / ^ ( c i r c l e | e l l i p s e ) (?: \s | $ ) / i;
41- const RADIAL_SIZE_REGEX = / ^ ( c l o s e s t - s i d e | c l o s e s t - c o r n e r | f a r t h e s t - s i d e | f a r t h e s t - c o r n e r | ( [ + - ] ? \d * \. ? \d + ) ( p x | % ) ? ) / i;
42- const RADIAL_POSITION_REGEX = / ^ (?: a t \s + ) ? ( c e n t e r | t o p | r i g h t | b o t t o m | l e f t | ( [ + - ] ? \d * \. ? \d + ) ( p x | % ) ? ) (?: \s + ( c e n t e r | t o p | r i g h t | b o t t o m | l e f t | ( [ + - ] ? \d * \. ? \d + ) ( p x | % ) ? ) ) ? / i;
43- type RadialExtent = 'closest-corner | closest-side | farthest-corner | farthest-side'
42+ const RADIAL_SIZE_REGEX =
43+ / ^ ( c l o s e s t - s i d e | c l o s e s t - c o r n e r | f a r t h e s t - s i d e | f a r t h e s t - c o r n e r | ( [ + - ] ? \d * \. ? \d + ) ( p x | % ) ? ) / i;
44+ const RADIAL_POSITION_REGEX =
45+ / ^ (?: a t \s + ) ? ( c e n t e r | t o p | r i g h t | b o t t o m | l e f t | ( [ + - ] ? \d * \. ? \d + ) ( p x | % ) ? ) (?: \s + ( c e n t e r | t o p | r i g h t | b o t t o m | l e f t | ( [ + - ] ? \d * \. ? \d + ) ( p x | % ) ? ) ) ? / i;
46+ type RadialExtent =
47+ 'closest-corner | closest-side | farthest-corner | farthest-side' ;
4448type RadialGradientBackgroundImage = {
4549 type : 'radialGradient' ,
4650 shape : 'circle' | 'ellipse' ,
@@ -49,16 +53,17 @@ type RadialGradientBackgroundImage = {
4953 colorStops : $ReadOnlyArray < {
5054 color : ColorStopColor ,
5155 position : ColorStopPosition ,
52- } >
53- }
56+ } > ,
57+ } ;
5458
5559// null color indicate that the transition hint syntax is used. e.g. red, 20%, blue
5660type ColorStopColor = ProcessedColorValue | null ;
5761// percentage or pixel value
5862type ColorStopPosition = number | string | null ;
5963
60- type ParsedBackgroundImageValue = LinearGradientBackgroundImage | RadialGradientBackgroundImage ;
61-
64+ type ParsedBackgroundImageValue =
65+ | LinearGradientBackgroundImage
66+ | RadialGradientBackgroundImage ;
6267
6368export default function processBackgroundImage (
6469 backgroundImage : ?( $ReadOnlyArray < BackgroundImageValue > | string ) ,
@@ -69,7 +74,7 @@ export default function processBackgroundImage(
6974 }
7075
7176 if ( typeof backgroundImage === 'string' ) {
72- result = parseCSSLinearGradient ( backgroundImage . replace ( / \n / g, ' ' ) ) ;
77+ result = parseBackgroundImageCSSString ( backgroundImage . replace ( / \n / g, ' ' ) ) ;
7378 } else if ( Array . isArray ( backgroundImage ) ) {
7479 for ( const bgImage of backgroundImage ) {
7580 const processedColorStops : Array < {
@@ -128,7 +133,8 @@ export default function processBackgroundImage(
128133 }
129134 }
130135
131- let direction : LinearGradientDirection = LINEAR_GRADIENT_DEFAULT_DIRECTION ;
136+ let direction : LinearGradientDirection =
137+ LINEAR_GRADIENT_DEFAULT_DIRECTION ;
132138 const bgDirection =
133139 bgImage . direction != null ? bgImage . direction . toLowerCase ( ) : null ;
134140
@@ -169,147 +175,173 @@ export default function processBackgroundImage(
169175 return result ;
170176}
171177
172- function parseCSSLinearGradient (
178+ function parseBackgroundImageCSSString (
173179 cssString : string ,
174180) : $ReadOnlyArray < ParsedBackgroundImageValue > {
175181 const gradients = [ ] ;
176182 let match ;
183+ // matches one or more linear-gradient or radial-gradient functions in CSS
184+ const gradientRegex =
185+ / ( l i n e a r | r a d i a l ) - g r a d i e n t \s * \( ( (?: \( [ ^ ) ] * \) | [ ^ ( ) ] ) * ) \) / gi;
177186
178- // matches one or more linear-gradient functions in CSS
179- const linearGradientRegex = / l i n e a r - g r a d i e n t \s * \( ( (?: \( [ ^ ) ] * \) | [ ^ ( ) ) ] ) * ) \) / gi;
180-
181- while ( ( match = linearGradientRegex . exec ( cssString ) ) ) {
182- const gradientContent = match [ 1 ] ;
183- const parts = gradientContent . split ( ',' ) ;
184- let direction : LinearGradientDirection = LINEAR_GRADIENT_DEFAULT_DIRECTION ;
185- const trimmedDirection = parts [ 0 ] . trim ( ) . toLowerCase ( ) ;
186-
187- if ( LINEAR_GRADIENT_ANGLE_UNIT_REGEX . test ( trimmedDirection ) ) {
188- const parsedAngle = getAngleInDegrees ( trimmedDirection ) ;
189- if ( parsedAngle != null ) {
190- direction = {
191- type : 'angle' ,
192- value : parsedAngle ,
193- } ;
194- parts . shift ( ) ;
195- } else {
196- // If an angle is invalid, return an empty array and do not apply any gradient. Same as web.
197- return [ ] ;
198- }
199- } else if ( LINEAR_GRADIENT_DIRECTION_REGEX . test ( trimmedDirection ) ) {
200- const parsedDirection = getDirectionForKeyword ( trimmedDirection ) ;
201- if ( parsedDirection != null ) {
202- direction = parsedDirection ;
203- parts . shift ( ) ;
204- } else {
205- // If a direction is invalid, return an empty array and do not apply any gradient. Same as web.
206- return [ ] ;
207- }
187+ while ( ( match = gradientRegex . exec ( cssString ) ) ) {
188+ const [ , type , gradientContent ] = match ;
189+ const isRadial = type . toLowerCase ( ) === 'radial' ;
190+
191+ const gradient = isRadial
192+ ? parseRadialGradientCSSString ( gradientContent )
193+ : parseLinearGradientCSSString ( gradientContent ) ;
194+
195+ if ( gradient != null ) {
196+ gradients . push ( gradient ) ;
208197 }
198+ }
209199
210- const colorStopsString = parts . join ( ',' ) ;
211- const colorStops = [ ] ;
212- // 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"]
213- const stops = colorStopsString . split ( / , (? ! [ ^ ( ] * \) ) / ) ;
214- let prevStop = null ;
215- for ( let i = 0 ; i < stops . length ; i ++ ) {
216- const stop = stops [ i ] ;
217- const trimmedStop = stop . trim ( ) . toLowerCase ( ) ;
218- // Match function like pattern or single words
219- const colorStopParts = trimmedStop . match ( / \S + \( [ ^ ) ] * \) | \S + / g) ;
220- if ( colorStopParts == null ) {
221- // If a color stop is invalid, return an empty array and do not apply any gradient. Same as web.
222- return [ ] ;
200+ return gradients ;
201+ }
202+
203+ function parseRadialGradientCSSString (
204+ cssString : string ,
205+ ) : RadialGradientBackgroundImage {
206+ return {
207+ type : 'radialGradient' ,
208+ shape : 'circle' ,
209+ size : 'closest-corner' ,
210+ position : 'center' ,
211+ colorStops : [ ] ,
212+ } ;
213+ }
214+
215+ function parseLinearGradientCSSString(
216+ gradientContent: string,
217+ ): LinearGradientBackgroundImage | null {
218+ const parts = gradientContent . split ( ',' ) ;
219+ let direction : LinearGradientDirection = LINEAR_GRADIENT_DEFAULT_DIRECTION ;
220+ const trimmedDirection = parts [ 0 ] . trim ( ) . toLowerCase ( ) ;
221+
222+ if ( LINEAR_GRADIENT_ANGLE_UNIT_REGEX . test ( trimmedDirection ) ) {
223+ const parsedAngle = getAngleInDegrees ( trimmedDirection ) ;
224+ if ( parsedAngle != null ) {
225+ direction = {
226+ type : 'angle' ,
227+ value : parsedAngle ,
228+ } ;
229+ parts . shift ( ) ;
230+ } else {
231+ // If an angle is invalid, return null and do not apply any gradient. Same as web.
232+ return null ;
233+ }
234+ } else if ( LINEAR_GRADIENT_DIRECTION_REGEX . test ( trimmedDirection ) ) {
235+ const parsedDirection = getDirectionForKeyword ( trimmedDirection ) ;
236+ if ( parsedDirection != null ) {
237+ direction = parsedDirection ;
238+ parts . shift ( ) ;
239+ } else {
240+ // If a direction is invalid, return null and do not apply any gradient. Same as web.
241+ return null ;
242+ }
243+ }
244+
245+ const colorStopsString = parts . join ( ',' ) ;
246+ const colorStops = [ ] ;
247+ // 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"]
248+ const stops = colorStopsString . split ( / , (? ! [ ^ ( ] * \) ) / ) ;
249+ let prevStop = null ;
250+ for ( let i = 0 ; i < stops . length ; i ++ ) {
251+ const stop = stops [ i ] ;
252+ const trimmedStop = stop . trim ( ) . toLowerCase ( ) ;
253+ // Match function like pattern or single words
254+ const colorStopParts = trimmedStop . match ( / \S + \( [ ^ ) ] * \) | \S + / g) ;
255+ if ( colorStopParts == null ) {
256+ // If a color stop is invalid, return null and do not apply any gradient. Same as web.
257+ return null ;
258+ }
259+ // Case 1: [color, position, position]
260+ if ( colorStopParts . length === 3 ) {
261+ const color = colorStopParts [ 0 ] ;
262+ const position1 = getPositionFromCSSValue ( colorStopParts [ 1 ] ) ;
263+ const position2 = getPositionFromCSSValue ( colorStopParts [ 2 ] ) ;
264+ const processedColor = processColor ( color ) ;
265+ if ( processedColor == null ) {
266+ // If a color is invalid, return null and do not apply any gradient. Same as web.
267+ return null ;
223268 }
224- // Case 1: [color, position, position]
225- if ( colorStopParts . length === 3 ) {
226- const color = colorStopParts [ 0 ] ;
227- const position1 = getPositionFromCSSValue ( colorStopParts [ 1 ] ) ;
228- const position2 = getPositionFromCSSValue ( colorStopParts [ 2 ] ) ;
229- const processedColor = processColor ( color ) ;
230- if ( processedColor == null ) {
231- // If a color is invalid, return an empty array and do not apply any gradient. Same as web.
232- return [ ] ;
233- }
234269
235- if ( position1 == null || position2 == null ) {
236- // If a position is invalid, return an empty array and do not apply any gradient. Same as web.
237- return [ ] ;
238- }
270+ if ( position1 == null || position2 == null ) {
271+ // If a position is invalid, return null and do not apply any gradient. Same as web.
272+ return null ;
273+ }
239274
275+ colorStops . push ( {
276+ color : processedColor ,
277+ position : position1 ,
278+ } ) ;
279+ colorStops . push ( {
280+ color : processedColor ,
281+ position : position2 ,
282+ } ) ;
283+ }
284+ // Case 2: [color, position]
285+ else if ( colorStopParts . length === 2 ) {
286+ const color = colorStopParts [ 0 ] ;
287+ const position = getPositionFromCSSValue ( colorStopParts [ 1 ] ) ;
288+ const processedColor = processColor ( color ) ;
289+ if ( processedColor == null ) {
290+ // If a color is invalid, return null and do not apply any gradient. Same as web.
291+ return null ;
292+ }
293+ if ( position == null ) {
294+ // If a position is invalid, return null and do not apply any gradient. Same as web.
295+ return null ;
296+ }
297+ colorStops . push ( {
298+ color : processedColor ,
299+ position ,
300+ } ) ;
301+ }
302+ // Case 3: [color]
303+ // Case 4: [position] => transition hint syntax
304+ else if ( colorStopParts . length === 1 ) {
305+ const position = getPositionFromCSSValue ( colorStopParts [ 0 ] ) ;
306+ if ( position != null ) {
307+ // handle invalid transition hint syntax. transition hint syntax must have color before and after the position. e.g. red, 20%, blue
308+ if (
309+ ( prevStop != null &&
310+ prevStop . length === 1 &&
311+ getPositionFromCSSValue ( prevStop [ 0 ] ) != null ) ||
312+ i === stops . length - 1 ||
313+ i === 0
314+ ) {
315+ // If the last stop is a transition hint syntax, return null and do not apply any gradient. Same as web.
316+ return null ;
317+ }
240318 colorStops.push({
241- color : processedColor ,
242- position : position1 ,
243- } ) ;
244- colorStops . push ( {
245- color : processedColor ,
246- position : position2 ,
319+ color : null ,
320+ position ,
247321 } );
248- }
249- // Case 2: [color, position]
250- else if ( colorStopParts . length === 2 ) {
251- const color = colorStopParts [ 0 ] ;
252- const position = getPositionFromCSSValue ( colorStopParts [ 1 ] ) ;
253- const processedColor = processColor ( color ) ;
322+ } else {
323+ const processedColor = processColor ( colorStopParts [ 0 ] ) ;
254324 if ( processedColor == null ) {
255- // If a color is invalid, return an empty array and do not apply any gradient. Same as web.
256- return [ ] ;
257- }
258- if ( position == null ) {
259- // If a position is invalid, return an empty array and do not apply any gradient. Same as web.
260- return [ ] ;
325+ // If a color is invalid, return null and do not apply any gradient. Same as web.
326+ return null ;
261327 }
262328 colorStops . push ( {
263329 color : processedColor ,
264- position ,
330+ position : null ,
265331 } ) ;
266332 }
267- // Case 3: [color]
268- // Case 4: [position] => transition hint syntax
269- else if ( colorStopParts . length === 1 ) {
270- const position = getPositionFromCSSValue ( colorStopParts [ 0 ] ) ;
271- if ( position != null ) {
272- // handle invalid transition hint syntax. transition hint syntax must have color before and after the position. e.g. red, 20%, blue
273- if (
274- ( prevStop != null &&
275- prevStop . length === 1 &&
276- getPositionFromCSSValue ( prevStop [ 0 ] ) != null ) ||
277- i === stops . length - 1 ||
278- i === 0
279- ) {
280- // If the last stop is a transition hint syntax, return an empty array and do not apply any gradient. Same as web.
281- return [ ] ;
282- }
283- colorStops . push ( {
284- color : null ,
285- position ,
286- } ) ;
287- } else {
288- const processedColor = processColor ( colorStopParts [ 0 ] ) ;
289- if ( processedColor == null ) {
290- // If a color is invalid, return an empty array and do not apply any gradient. Same as web.
291- return [ ] ;
292- }
293- colorStops.push({
294- color : processedColor ,
295- position : null ,
296- } );
297- }
298- } else {
299- // If a color stop is invalid, return an empty array and do not apply any gradient. Same as web.
300- return [ ] ;
301- }
302- prevStop = colorStopParts;
333+ } else {
334+ // If a color stop is invalid, return null and do not apply any gradient. Same as web.
335+ return null ;
303336 }
304-
305- gradients . push ( {
306- type : 'linearGradient' ,
307- direction ,
308- colorStops ,
309- } );
337+ prevStop = colorStopParts;
310338 }
311339
312- return gradients ;
340+ return {
341+ type : 'linearGradient' ,
342+ direction ,
343+ colorStops ,
344+ } ;
313345}
314346
315347function getDirectionForKeyword ( direction ?: string ) : ?LinearGradientDirection {
0 commit comments