@@ -22,11 +22,13 @@ type LinearGradientDirection =
2222 | { type : 'angle' , value : number }
2323 | { type : 'keyword' , value : string } ;
2424
25+ type ColorStopColor = ProcessedColorValue | null ;
26+
2527type ParsedGradientValue = {
2628 type : 'linearGradient' ,
2729 direction : LinearGradientDirection ,
2830 colorStops : $ReadOnlyArray < {
29- color : ProcessedColorValue ,
31+ color : ColorStopColor ,
3032 position : number ,
3133 } > ,
3234} ;
@@ -49,33 +51,52 @@ export default function processBackgroundImage(
4951 } else if ( Array . isArray ( backgroundImage ) ) {
5052 for ( const bgImage of backgroundImage ) {
5153 const processedColorStops : Array < {
52- color : ProcessedColorValue ,
54+ color : ColorStopColor ,
5355 position : number | null ,
5456 } > = [ ] ;
5557 for ( let index = 0 ; index < bgImage . colorStops . length ; index ++ ) {
5658 const colorStop = bgImage . colorStops [ index ] ;
57- const processedColor = processColor ( colorStop . color ) ;
58- if ( processedColor == null ) {
59- // If a color is invalid, return an empty array and do not apply gradient. Same as web.
60- return [ ] ;
61- }
62- if ( colorStop . positions != null && colorStop . positions . length > 0 ) {
63- for ( const position of colorStop . positions ) {
64- if ( position . endsWith ( '%' ) ) {
65- processedColorStops . push ( {
66- color : processedColor ,
67- position : parseFloat ( position ) / 100 ,
68- } ) ;
69- } else {
70- // If a position is invalid, return an empty array and do not apply gradient. Same as web.
71- return [ ] ;
72- }
59+ const positions = colorStop . positions ;
60+ // Color transition hint syntax (red, 20%, blue)
61+ if (
62+ colorStop . color == null &&
63+ Array . isArray ( positions ) &&
64+ positions . length === 1
65+ ) {
66+ const position = positions [ 0 ] ;
67+ if ( typeof position === 'string' && position . endsWith ( '%' ) ) {
68+ processedColorStops . push ( {
69+ color : null ,
70+ position : parseFloat ( position ) / 100 ,
71+ } ) ;
72+ } else {
73+ // If a position is invalid, return an empty array and do not apply gradient. Same as web.
74+ return [ ] ;
7375 }
7476 } else {
75- processedColorStops . push ( {
76- color : processedColor ,
77- position : null ,
78- } ) ;
77+ const processedColor = processColor ( colorStop . color ) ;
78+ if ( processedColor == null ) {
79+ // If a color is invalid, return an empty array and do not apply gradient. Same as web.
80+ return [ ] ;
81+ }
82+ if ( positions != null && positions . length > 0 ) {
83+ for ( const position of positions ) {
84+ if ( position . endsWith ( '%' ) ) {
85+ processedColorStops . push ( {
86+ color : processedColor ,
87+ position : parseFloat ( position ) / 100 ,
88+ } ) ;
89+ } else {
90+ // If a position is invalid, return an empty array and do not apply gradient. Same as web.
91+ return [ ] ;
92+ }
93+ }
94+ } else {
95+ processedColorStops . push ( {
96+ color : processedColor ,
97+ position : null ,
98+ } ) ;
99+ }
79100 }
80101 }
81102
@@ -169,47 +190,85 @@ function parseCSSLinearGradient(
169190 // 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.
170191 return [ ] ;
171192 }
172- colorStopRegex . lastIndex = 0 ;
173193
194+ const colorStopsString = parts . join ( ',' ) ;
174195 const colorStops = [ ] ;
175- const fullColorStopsStr = parts . join ( ',' ) ;
176- let colorStopMatch ;
177- while ( ( colorStopMatch = colorStopRegex . exec ( fullColorStopsStr ) ) ) {
178- const [ , color , position1 , position2 ] = colorStopMatch ;
179- const processedColor = processColor ( color . trim ( ) . toLowerCase ( ) ) ;
180- if ( processedColor == null ) {
181- // 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 colorStopParts = trimmedStop . match ( / \S + \( [ ^ ) ] * \) | \S + / g) ;
202+ if ( colorStopParts == null ) {
203+ // If a color stop is invalid, return an empty array and do not apply any gradient. Same as web.
182204 return [ ] ;
183205 }
184-
185- if ( typeof position1 !== 'undefined' ) {
186- if ( position1 . endsWith ( '%' ) ) {
206+ // Case 1: [color, position, position]
207+ if ( colorStopParts . length === 3 ) {
208+ const color = colorStopParts [ 0 ] ;
209+ const position1 = colorStopParts [ 1 ] ;
210+ const position2 = colorStopParts [ 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 ( '%' ) ) {
187217 colorStops . push ( {
188218 color : processedColor ,
189219 position : parseFloat ( position1 ) / 100 ,
190220 } ) ;
221+ colorStops . push ( {
222+ color : processedColor ,
223+ position : parseFloat ( position2 ) / 100 ,
224+ } ) ;
191225 } else {
192226 // If a position is invalid, return an empty array and do not apply any gradient. Same as web.
193227 return [ ] ;
194228 }
195- } else {
196- colorStops . push ( {
197- color : processedColor ,
198- position : null ,
199- } ) ;
200229 }
201-
202- if ( typeof position2 !== 'undefined ') {
203- if ( position2 . endsWith ( '% ') ) {
230+ // Case 2: [color, position]
231+ else if ( colorStopParts . length === 2 ) {
232+ const color = colorStopParts [ 0 ] ;
233+ const position = colorStopParts [ 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 ( '% ') ) {
204240 colorStops . push ( {
205241 color : processedColor ,
206- position : parseFloat ( position2 ) / 100 ,
242+ position : parseFloat ( position ) / 100 ,
207243 } ) ;
208244 } else {
209245 // If a position is invalid, return an empty array and do not apply any gradient. Same as web.
210246 return [ ] ;
211247 }
212248 }
249+ // Case 3: [color]
250+ // Case 4: [position] => transition hint syntax
251+ else if ( colorStopParts . length === 1 ) {
252+ if ( colorStopParts [ 0 ] . endsWith ( '% ') ) {
253+ colorStops . push ( {
254+ color : null ,
255+ position : parseFloat ( colorStopParts [ 0 ] ) / 100 ,
256+ } ) ;
257+ } else {
258+ const processedColor = processColor ( colorStopParts [ 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+ }
213272 }
214273
215274 const fixedColorStops = getFixedColorStops(colorStops);
@@ -286,15 +345,15 @@ function getAngleInDegrees(angle?: string): ?number {
286345// https://drafts.csswg.org/css-images-4/#color-stop-fixup
287346function getFixedColorStops (
288347 colorStops : $ReadOnlyArray < {
289- color : ProcessedColorValue ,
348+ color : ColorStopColor ,
290349 position : number | null ,
291350 } > ,
292351): Array< {
293- color : ProcessedColorValue ,
352+ color : ColorStopColor ,
294353 position : number ,
295354} > {
296355 let fixedColorStops : Array < {
297- color : ProcessedColorValue ,
356+ color : ColorStopColor ,
298357 position : number ,
299358 } > = [ ] ;
300359 let hasNullPositions = false ;
0 commit comments