@@ -22,15 +22,17 @@ type LinearGradientDirection =
2222 | { type : 'angle' , value : number }
2323 | { type : 'keyword' , value : string } ;
2424
25- // null color stops indicate that the transition hint syntax is used. e.g. red, 20%, blue
25+ // null color indicate that the transition hint syntax is used. e.g. red, 20%, blue
2626type ColorStopColor = ProcessedColorValue | null ;
27+ // percentage or pixel value
28+ type ColorStopPosition = number | string | null ;
2729
2830type ParsedGradientValue = {
2931 type : 'linearGradient' ,
3032 direction : LinearGradientDirection ,
3133 colorStops : $ReadOnlyArray < {
3234 color : ColorStopColor ,
33- position : number ,
35+ position : ColorStopPosition ,
3436 } > ,
3537} ;
3638
@@ -53,7 +55,7 @@ export default function processBackgroundImage(
5355 for ( const bgImage of backgroundImage ) {
5456 const processedColorStops : Array < {
5557 color : ColorStopColor ,
56- position : number | null ,
58+ position : ColorStopPosition ,
5759 } > = [ ] ;
5860 for ( let index = 0 ; index < bgImage . colorStops . length ; index ++ ) {
5961 const colorStop = bgImage . colorStops [ index ] ;
@@ -65,10 +67,13 @@ export default function processBackgroundImage(
6567 positions . length === 1
6668 ) {
6769 const position = positions [ 0 ] ;
68- if ( typeof position === 'string' && position . endsWith ( '%' ) ) {
70+ if (
71+ typeof position === 'number' ||
72+ ( typeof position === 'string' && position . endsWith ( '%' ) )
73+ ) {
6974 processedColorStops . push ( {
7075 color : null ,
71- position : parseFloat ( position ) / 100 ,
76+ position,
7277 } ) ;
7378 } else {
7479 // If a position is invalid, return an empty array and do not apply gradient. Same as web.
@@ -82,10 +87,13 @@ export default function processBackgroundImage(
8287 }
8388 if ( positions != null && positions . length > 0 ) {
8489 for ( const position of positions ) {
85- if ( position . endsWith ( '%' ) ) {
90+ if (
91+ typeof position === 'number' ||
92+ ( typeof position === 'string' && position . endsWith ( '%' ) )
93+ ) {
8694 processedColorStops . push ( {
8795 color : processedColor ,
88- position : parseFloat ( position ) / 100 ,
96+ position,
8997 } ) ;
9098 } else {
9199 // If a position is invalid, return an empty array and do not apply gradient. Same as web.
@@ -131,12 +139,10 @@ export default function processBackgroundImage(
131139 }
132140 }
133141
134- const fixedColorStops = getFixedColorStops ( processedColorStops ) ;
135-
136142 result = result . concat ( {
137143 type : 'linearGradient' ,
138144 direction,
139- colorStops : fixedColorStops ,
145+ colorStops : processedColorStops ,
140146 } ) ;
141147 }
142148 }
@@ -186,8 +192,9 @@ function parseCSSLinearGradient(
186192 const colorStops = [ ] ;
187193 // 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"]
188194 const stops = colorStopsString . split ( / , (? ! [ ^ ( ] * \) ) / ) ;
189- let lastStop = null ;
190- for ( const stop of stops ) {
195+ let prevStop = null ;
196+ for ( let i = 0 ; i < stops . length ; i ++ ) {
197+ const stop = stops [ i ] ;
191198 const trimmedStop = stop . trim ( ) . toLowerCase ( ) ;
192199 // Match function like pattern or single words
193200 const colorStopParts = trimmedStop . match ( / \S + \( [ ^ ) ] * \) | \S + / g) ;
@@ -198,61 +205,65 @@ function parseCSSLinearGradient(
198205 // Case 1: [color, position, position]
199206 if ( colorStopParts . length === 3 ) {
200207 const color = colorStopParts [ 0 ] ;
201- const position1 = colorStopParts [ 1 ] ;
202- const position2 = colorStopParts [ 2 ] ;
208+ const position1 = getPositionFromCSSValue ( colorStopParts [ 1 ] ) ;
209+ const position2 = getPositionFromCSSValue ( colorStopParts [ 2 ] ) ;
203210 const processedColor = processColor ( color ) ;
204211 if ( processedColor == null ) {
205212 // If a color is invalid, return an empty array and do not apply any gradient. Same as web.
206213 return [ ] ;
207214 }
208- if ( position1 . endsWith ( '%' ) && position2 . endsWith ( '%' ) ) {
209- colorStops . push ( {
210- color : processedColor ,
211- position : parseFloat ( position1 ) / 100 ,
212- } ) ;
213- colorStops . push ( {
214- color : processedColor ,
215- position : parseFloat ( position2 ) / 100 ,
216- } ) ;
217- } else {
215+
216+ if ( position1 == null || position2 == null ) {
218217 // If a position is invalid, return an empty array and do not apply any gradient. Same as web.
219218 return [ ] ;
220219 }
220+
221+ colorStops . push ( {
222+ color : processedColor ,
223+ position : position1 ,
224+ } ) ;
225+ colorStops . push ( {
226+ color : processedColor ,
227+ position : position2 ,
228+ } ) ;
221229 }
222230 // Case 2: [color, position]
223231 else if ( colorStopParts . length === 2 ) {
224232 const color = colorStopParts [ 0 ] ;
225- const position = colorStopParts [ 1 ] ;
233+ const position = getPositionFromCSSValue ( colorStopParts [ 1 ] ) ;
226234 const processedColor = processColor ( color ) ;
227235 if ( processedColor == null ) {
228236 // If a color is invalid, return an empty array and do not apply any gradient. Same as web.
229237 return [ ] ;
230238 }
231- if ( position . endsWith ( '% ') ) {
232- colorStops . push ( {
233- color : processedColor ,
234- position : parseFloat ( position ) / 100 ,
235- } ) ;
236- } else {
239+ if ( position == null ) {
237240 // If a position is invalid, return an empty array and do not apply any gradient. Same as web.
238241 return [ ] ;
239242 }
243+ colorStops . push ( {
244+ color : processedColor ,
245+ position ,
246+ } ) ;
240247 }
241248 // Case 3: [color]
242249 // Case 4: [position] => transition hint syntax
243250 else if ( colorStopParts . length === 1 ) {
244- if ( colorStopParts [ 0 ] . endsWith ( '% ') ) {
251+ const position = getPositionFromCSSValue ( colorStopParts [ 0 ] ) ;
252+ if ( position != null ) {
253+ // handle invalid transition hint syntax. transition hint syntax must have color before and after the position. e.g. red, 20%, blue
245254 if (
246- lastStop != null &&
247- lastStop . length === 1 &&
248- lastStop [ 0 ] . endsWith ( '%' )
255+ ( prevStop != null &&
256+ prevStop . length === 1 &&
257+ getPositionFromCSSValue ( prevStop [ 0 ] ) != null ) ||
258+ i === stops . length - 1 ||
259+ i === 0
249260 ) {
250261 // If the last stop is a transition hint syntax, return an empty array and do not apply any gradient. Same as web.
251262 return [ ] ;
252263 }
253264 colorStops . push ( {
254265 color : null ,
255- position : parseFloat ( colorStopParts [ 0 ] ) / 100 ,
266+ position ,
256267 } ) ;
257268 } else {
258269 const processedColor = processColor ( colorStopParts [ 0 ] ) ;
@@ -269,15 +280,13 @@ function parseCSSLinearGradient(
269280 // If a color stop is invalid, return an empty array and do not apply any gradient. Same as web.
270281 return [ ] ;
271282 }
272- lastStop = colorStopParts;
283+ prevStop = colorStopParts;
273284 }
274285
275- const fixedColorStops = getFixedColorStops ( colorStops ) ;
276-
277286 gradients . push ( {
278287 type : 'linearGradient' ,
279288 direction ,
280- colorStops : fixedColorStops ,
289+ colorStops ,
281290 } );
282291 }
283292
@@ -343,79 +352,12 @@ function getAngleInDegrees(angle?: string): ?number {
343352 }
344353}
345354
346- // https://drafts.csswg.org/css-images-4/#color-stop-fixup
347- function getFixedColorStops (
348- colorStops : $ReadOnlyArray < {
349- color : ColorStopColor ,
350- position : number | null ,
351- } > ,
352- ): Array< {
353- color : ColorStopColor ,
354- position : number ,
355- } > {
356- let fixedColorStops : Array < {
357- color : ColorStopColor ,
358- position : number ,
359- } > = [ ] ;
360- let hasNullPositions = false ;
361- let maxPositionSoFar = colorStops [ 0 ] . position ?? 0 ;
362- for ( let i = 0 ; i < colorStops . length ; i ++ ) {
363- const colorStop = colorStops [ i ] ;
364- let newPosition = colorStop . position ;
365- if ( newPosition === null ) {
366- // Step 1:
367- // If the first color stop does not have a position,
368- // set its position to 0%. If the last color stop does not have a position,
369- // set its position to 100%.
370- if ( i === 0 ) {
371- newPosition = 0 ;
372- } else if ( i === colorStops . length - 1 ) {
373- newPosition = 1 ;
374- }
375- }
376- // Step 2:
377- // If a color stop or transition hint has a position
378- // that is less than the specified position of any color stop or transition hint
379- // before it in the list, set its position to be equal to the
380- // largest specified position of any color stop or transition hint before it.
381- if ( newPosition !== null ) {
382- newPosition = Math . max ( newPosition , maxPositionSoFar ) ;
383- fixedColorStops [ i ] = {
384- color : colorStop . color ,
385- position : newPosition ,
386- } ;
387- maxPositionSoFar = newPosition ;
388- } else {
389- hasNullPositions = true ;
390- }
355+ function getPositionFromCSSValue ( position : string ) {
356+ if ( position . endsWith ( 'px' ) ) {
357+ return parseFloat ( position ) ;
391358 }
392359
393- // Step 3:
394- // If any color stop still does not have a position,
395- // then, for each run of adjacent color stops without positions,
396- // set their positions so that they are evenly spaced between the preceding and
397- // following color stops with positions.
398- if ( hasNullPositions ) {
399- let lastDefinedIndex = 0 ;
400- for ( let i = 1 ; i < fixedColorStops . length ; i ++ ) {
401- if ( fixedColorStops [ i ] !== undefined ) {
402- const unpositionedStops = i - lastDefinedIndex - 1 ;
403- if ( unpositionedStops > 0 ) {
404- const startPosition = fixedColorStops [ lastDefinedIndex ] . position ;
405- const endPosition = fixedColorStops [ i ] . position ;
406- const increment =
407- ( endPosition - startPosition ) / ( unpositionedStops + 1 ) ;
408- for ( let j = 1 ; j <= unpositionedStops ; j ++ ) {
409- fixedColorStops [ lastDefinedIndex + j ] = {
410- color : colorStops [ lastDefinedIndex + j ] . color ,
411- position : startPosition + increment * j ,
412- } ;
413- }
414- }
415- lastDefinedIndex = i ;
416- }
417- }
360+ if (position.endsWith('%')) {
361+ return position ;
418362 }
419-
420- return fixedColorStops ;
421363}
0 commit comments