@@ -49,7 +49,6 @@ const integerPattern = '[-+]?\\d+';
4949const numberPattern = `((${ integerPattern } )(\\.\\d+)?|[-+]?(\\.\\d+))(e[-+]?${ integerPattern } )?` ;
5050const percentPattern = `(${ numberPattern } )(%)` ;
5151const identRegEx = new RegExp ( `^${ identPattern } $` , 'i' ) ;
52- const integerRegEx = new RegExp ( `^${ integerPattern } $` ) ;
5352const numberRegEx = new RegExp ( `^${ numberPattern } $` ) ;
5453const percentRegEx = new RegExp ( `^${ percentPattern } $` ) ;
5554const stringRegEx = / ^ ( " [ ^ " ] * " | ' [ ^ ' ] * ' ) $ / ;
@@ -64,10 +63,9 @@ const anglePattern = `(${numberPattern})(deg|grad|rad|turn)`;
6463const lengthPattern = `(${ numberPattern } )(ch|cm|r?em|ex|in|lh|mm|pc|pt|px|q|vh|vmin|vmax|vw)` ;
6564const angleRegEx = new RegExp ( `^${ anglePattern } $` , 'i' ) ;
6665const calcRegEx = / ^ c a l c \( \s * ( .+ ) \s * \) $ / i;
67- const colorRegEx1 = / ^ # ( [ 0 - 9 a - f A - F ] { 3 , 4 } ) { 1 , 2 } $ / ;
68- const colorRegEx2 = / ^ r g b \( ( [ ^ ) ] * ) \) $ / ;
69- const colorRegEx3 = / ^ r g b a \( ( [ ^ ) ] * ) \) $ / ;
70- const colorRegEx4 = / ^ h s l a ? \( \s * ( - ? \d + | - ? \d * .\d + ) \s * , \s * ( - ? \d + | - ? \d * .\d + ) % \s * , \s * ( - ? \d + | - ? \d * .\d + ) % \s * ( , \s * ( - ? \d + | - ? \d * .\d + ) \s * ) ? \) / ;
66+ const colorHexRegEx = / ^ # ( [ 0 - 9 a - f ] { 3 , 4 } ) { 1 , 2 } $ / i;
67+ const colorFnSeparators = [ ',' , '/' , ' ' ] ;
68+ const colorFnRegex = / ^ ( h s l | r g b ) a ? \( \s * ( .+ ) \s * \) $ / i;
7169const lengthRegEx = new RegExp ( `^${ lengthPattern } $` , 'i' ) ;
7270const numericRegEx = new RegExp ( `^(${ numberPattern } )(%|${ identPattern } )?$` , 'i' ) ;
7371const timeRegEx = new RegExp ( `^(${ numberPattern } )(m?s)$` , 'i' ) ;
@@ -227,6 +225,45 @@ exports.parseLengthOrPercentage = function parseLengthOrPercentage(val, resolve)
227225 return exports . parseLength ( val , resolve ) || exports . parsePercentage ( val , resolve ) ;
228226} ;
229227
228+ /**
229+ * https://drafts.csswg.org/cssom/#ref-for-alphavalue-def
230+ * https://drafts.csswg.org/cssom/#ref-for-alphavalue-def
231+ *
232+ * Browsers store a gradient alpha value as an 8 bit unsigned integer value when
233+ * given as a percentage, while they store a gradient alpha value as a decimal
234+ * value when given as a number, or when given an opacity value as a number or
235+ * percentage.
236+ */
237+ exports . parseAlpha = function parseAlpha ( val , is8Bit = false ) {
238+ if ( val === '' ) {
239+ return val ;
240+ }
241+ const variable = exports . parseCustomVariable ( val ) ;
242+ if ( variable ) {
243+ return variable ;
244+ }
245+ let parsed = exports . parseNumber ( val ) ;
246+ if ( parsed !== undefined ) {
247+ is8Bit = false ;
248+ val = Math . min ( 1 , Math . max ( 0 , parsed ) ) * 100 ;
249+ } else if ( ( parsed = exports . parsePercentage ( val , true ) ) ) {
250+ val = Math . min ( 100 , Math . max ( 0 , parsed . slice ( 0 , - 1 ) ) ) ;
251+ } else {
252+ return undefined ;
253+ }
254+
255+ if ( ! is8Bit ) {
256+ return serializeNumber ( val / 100 ) ;
257+ }
258+
259+ // Fix JS precision (eg. 50 * 2.55 === 127.499... instead of 127.5) with toPrecision(15)
260+ const alpha = Math . round ( ( val * 2.55 ) . toPrecision ( 15 ) ) ;
261+ const integer = Math . round ( alpha / 2.55 ) ;
262+ const hasInteger = Math . round ( ( integer * 2.55 ) . toPrecision ( 15 ) ) === alpha ;
263+
264+ return String ( hasInteger ? integer / 100 : Math . round ( alpha / 0.255 ) / 1000 ) ;
265+ } ;
266+
230267/**
231268 * https://drafts.csswg.org/css-values-4/#angles
232269 * https://drafts.csswg.org/cssom/#ref-for-angle-value
@@ -572,115 +609,137 @@ exports.parseColor = function parseColor(val) {
572609 return variable ;
573610 }
574611
575- var red ,
576- green ,
577- blue ,
578- hue ,
579- saturation ,
580- lightness ,
581- alpha = 1 ;
582- var parts ;
583- var res = colorRegEx1 . exec ( val ) ;
584- // is it #aaa, #ababab, #aaaa, #abababaa
585- if ( res ) {
586- var defaultHex = val . substr ( 1 ) ;
587- var hex = val . substr ( 1 ) ;
588- if ( hex . length === 3 || hex . length === 4 ) {
589- hex = hex [ 0 ] + hex [ 0 ] + hex [ 1 ] + hex [ 1 ] + hex [ 2 ] + hex [ 2 ] ;
612+ const rgb = [ ] ;
590613
591- if ( defaultHex . length === 4 ) {
592- hex = hex + defaultHex [ 3 ] + defaultHex [ 3 ] ;
593- }
594- }
595- red = parseInt ( hex . substr ( 0 , 2 ) , 16 ) ;
596- green = parseInt ( hex . substr ( 2 , 2 ) , 16 ) ;
597- blue = parseInt ( hex . substr ( 4 , 2 ) , 16 ) ;
598- if ( hex . length === 8 ) {
599- var hexAlpha = hex . substr ( 6 , 2 ) ;
600- var hexAlphaToRgbaAlpha = Number ( ( parseInt ( hexAlpha , 16 ) / 255 ) . toFixed ( 3 ) ) ;
601-
602- return 'rgba(' + red + ', ' + green + ', ' + blue + ', ' + hexAlphaToRgbaAlpha + ')' ;
603- }
604- return 'rgb(' + red + ', ' + green + ', ' + blue + ')' ;
605- }
614+ /**
615+ * <hex-color>
616+ * value should be `#` followed by 3, 4, 6, or 8 hexadecimal digits
617+ * value should be resolved to <rgb()> | <rgba()>
618+ * value should be resolved to <rgb()> if <alpha> === 1
619+ */
620+ const hex = colorHexRegEx . exec ( val ) ;
606621
607- res = colorRegEx2 . exec ( val ) ;
608- if ( res ) {
609- parts = res [ 1 ] . split ( / \s * , \s * / ) ;
610- if ( parts . length !== 3 ) {
611- return undefined ;
622+ if ( hex ) {
623+ const [ , n1 , n2 , n3 , n4 , n5 , n6 , n7 , n8 ] = val ;
624+ let alpha = 1 ;
625+
626+ switch ( val . length ) {
627+ case 4 :
628+ rgb . push ( Number ( `0x${ n1 } ${ n1 } ` ) , Number ( `0x${ n2 } ${ n2 } ` ) , Number ( `0x${ n3 } ${ n3 } ` ) ) ;
629+ break ;
630+ case 5 :
631+ rgb . push ( Number ( `0x${ n1 } ${ n1 } ` ) , Number ( `0x${ n2 } ${ n2 } ` ) , Number ( `0x${ n3 } ${ n3 } ` ) ) ;
632+ alpha = Number ( `0x${ n4 } ${ n4 } ` / 255 ) ;
633+ break ;
634+ case 7 :
635+ rgb . push ( Number ( `0x${ n1 } ${ n2 } ` ) , Number ( `0x${ n3 } ${ n4 } ` ) , Number ( `0x${ n5 } ${ n6 } ` ) ) ;
636+ break ;
637+ case 9 :
638+ rgb . push ( Number ( `0x${ n1 } ${ n2 } ` ) , Number ( `0x${ n3 } ${ n4 } ` ) , Number ( `0x${ n5 } ${ n6 } ` ) ) ;
639+ alpha = Number ( `0x${ n7 } ${ n8 } ` / 255 ) ;
640+ break ;
641+ default :
642+ return undefined ;
612643 }
613- if ( parts . every ( percentRegEx . test . bind ( percentRegEx ) ) ) {
614- red = Math . floor ( ( parseFloat ( parts [ 0 ] . slice ( 0 , - 1 ) ) * 255 ) / 100 ) ;
615- green = Math . floor ( ( parseFloat ( parts [ 1 ] . slice ( 0 , - 1 ) ) * 255 ) / 100 ) ;
616- blue = Math . floor ( ( parseFloat ( parts [ 2 ] . slice ( 0 , - 1 ) ) * 255 ) / 100 ) ;
617- } else if ( parts . every ( integerRegEx . test . bind ( integerRegEx ) ) ) {
618- red = parseInt ( parts [ 0 ] , 10 ) ;
619- green = parseInt ( parts [ 1 ] , 10 ) ;
620- blue = parseInt ( parts [ 2 ] , 10 ) ;
621- } else {
622- return undefined ;
644+
645+ if ( alpha == 1 ) {
646+ return `rgb(${ rgb . join ( ', ' ) } )` ;
623647 }
624- red = Math . min ( 255 , Math . max ( 0 , red ) ) ;
625- green = Math . min ( 255 , Math . max ( 0 , green ) ) ;
626- blue = Math . min ( 255 , Math . max ( 0 , blue ) ) ;
627- return 'rgb(' + red + ', ' + green + ', ' + blue + ')' ;
648+ return `rgba(${ rgb . join ( ', ' ) } , ${ + alpha . toFixed ( 3 ) } )` ;
628649 }
629650
630- res = colorRegEx3 . exec ( val ) ;
631- if ( res ) {
632- parts = res [ 1 ] . split ( / \s * , \s * / ) ;
633- if ( parts . length !== 4 ) {
651+ /**
652+ * <rgb()> | <rgba()>
653+ * <hsl()> | <hsla()>
654+ * <arg1>, <arg2>, <arg3>[, <alpha>]? or <arg1> <arg2> <arg3>[ / <alpha>]?
655+ * <alpha> should be <number> or <percentage>
656+ * <alpha> should be resolved to <number> and clamped to 0-1
657+ * value should be resolved to <rgb()> if <alpha> === 1
658+ */
659+ const fn = colorFnRegex . exec ( val ) ;
660+ if ( fn ) {
661+ let [ , name , args ] = fn ;
662+ const [ [ arg1 , arg2 , arg3 , arg4 = 1 ] , separators ] = exports . splitFnArgs ( args , colorFnSeparators ) ;
663+ const [ sep1 , sep2 , sep3 ] = separators . map ( s => ( s === ' ' ? s : s . trim ( ) ) ) ;
664+ const alpha = exports . parseAlpha ( arg4 , true ) ;
665+
666+ name = name . toLowerCase ( ) ;
667+
668+ if (
669+ ! alpha ||
670+ sep1 !== sep2 ||
671+ ( ( sep3 && ! ( sep3 === ',' && sep1 === ',' ) ) || ( sep3 === '/' && sep1 === ' ' ) )
672+ ) {
634673 return undefined ;
635674 }
636- if ( parts . slice ( 0 , 3 ) . every ( percentRegEx . test . bind ( percentRegEx ) ) ) {
637- red = Math . floor ( ( parseFloat ( parts [ 0 ] . slice ( 0 , - 1 ) ) * 255 ) / 100 ) ;
638- green = Math . floor ( ( parseFloat ( parts [ 1 ] . slice ( 0 , - 1 ) ) * 255 ) / 100 ) ;
639- blue = Math . floor ( ( parseFloat ( parts [ 2 ] . slice ( 0 , - 1 ) ) * 255 ) / 100 ) ;
640- alpha = parseFloat ( parts [ 3 ] ) ;
641- } else if ( parts . slice ( 0 , 3 ) . every ( integerRegEx . test . bind ( integerRegEx ) ) ) {
642- red = parseInt ( parts [ 0 ] , 10 ) ;
643- green = parseInt ( parts [ 1 ] , 10 ) ;
644- blue = parseInt ( parts [ 2 ] , 10 ) ;
645- alpha = parseFloat ( parts [ 3 ] ) ;
646- } else {
647- return undefined ;
648- }
649- if ( isNaN ( alpha ) ) {
650- alpha = 1 ;
651- }
652- red = Math . min ( 255 , Math . max ( 0 , red ) ) ;
653- green = Math . min ( 255 , Math . max ( 0 , green ) ) ;
654- blue = Math . min ( 255 , Math . max ( 0 , blue ) ) ;
655- alpha = Math . min ( 1 , Math . max ( 0 , alpha ) ) ;
656- if ( alpha === 1 ) {
657- return 'rgb(' + red + ', ' + green + ', ' + blue + ')' ;
658- }
659- return 'rgba(' + red + ', ' + green + ', ' + blue + ', ' + alpha + ')' ;
660- }
661675
662- res = colorRegEx4 . exec ( val ) ;
663- if ( res ) {
664- const [ , _hue , _saturation , _lightness , _alphaString = '' ] = res ;
665- const _alpha = parseFloat ( _alphaString . replace ( ',' , '' ) . trim ( ) ) ;
666- if ( ! _hue || ! _saturation || ! _lightness ) {
667- return undefined ;
676+ /**
677+ * <hsl()> | <hsla()>
678+ * <hue> should be <angle> or <number>
679+ * <hue> should be resolved to <number> and clamped to 0-360 (540 -> 180)
680+ * <saturation> and <lightness> should be <percentage> and clamped to 0-100%
681+ * value should be resolved to <rgb()> or <rgba()>
682+ */
683+ if ( name === 'hsl' ) {
684+ const hsl = [ ] ;
685+ let hue ;
686+ if ( ( hue = exports . parseNumber ( arg1 ) ) ) {
687+ hsl . push ( ( hue /= 60 ) ) ;
688+ } else if ( ( hue = exports . parseAngle ( arg1 , true ) ) ) {
689+ hsl . push ( hue . slice ( 0 , - 3 ) / 60 ) ;
690+ } else {
691+ return undefined ;
692+ }
693+ [ arg2 , arg3 ] . forEach ( val => {
694+ if ( ( val = exports . parsePercentage ( val , true ) ) ) {
695+ return hsl . push ( Math . min ( 100 , Math . max ( 0 , val . slice ( 0 , - 1 ) ) ) / 100 ) ;
696+ }
697+ } ) ;
698+ if ( hsl . length < 3 ) {
699+ return undefined ;
700+ }
701+ rgb . push ( ...hslToRgb ( ...hsl ) ) ;
668702 }
669- hue = parseFloat ( _hue ) ;
670- saturation = parseInt ( _saturation , 10 ) ;
671- lightness = parseInt ( _lightness , 10 ) ;
672- if ( _alpha && numberRegEx . test ( _alpha ) ) {
673- alpha = parseFloat ( _alpha ) ;
703+
704+ /**
705+ * <rgb()> | <rgba()>
706+ * rgb args should all be <number> or <percentage>
707+ * rgb args should be resolved to <number> and clamped to 0-255
708+ */
709+ if ( name === 'rgb' ) {
710+ const types = new Set ( ) ;
711+ [ arg1 , arg2 , arg3 ] . forEach ( val => {
712+ const number = exports . parseNumber ( val ) ;
713+ if ( number ) {
714+ types . add ( 'number' ) ;
715+ rgb . push ( Math . round ( Math . min ( 255 , Math . max ( 0 , number ) ) ) ) ;
716+ return ;
717+ }
718+ const percentage = exports . parsePercentage ( val , true ) ;
719+ if ( percentage ) {
720+ types . add ( 'percent' ) ;
721+ rgb . push ( Math . round ( Math . min ( 255 , Math . max ( 0 , ( percentage . slice ( 0 , - 1 ) / 100 ) * 255 ) ) ) ) ;
722+ return ;
723+ }
724+ } ) ;
725+ if ( rgb . length < 3 || types . size > 1 ) {
726+ return undefined ;
727+ }
674728 }
675729
676- const [ r , g , b ] = hslToRgb ( hue , saturation / 100 , lightness / 100 ) ;
677- if ( ! _alphaString || alpha === 1 ) {
678- return 'rgb(' + r + ', ' + g + ', ' + b + ')' ;
730+ if ( alpha < 1 ) {
731+ return `rgba(${ rgb . join ( ', ' ) } , ${ alpha } )` ;
679732 }
680- return 'rgba(' + r + ' , ' + g + ', ' + b + ', ' + alpha + ')' ;
733+ return `rgb( ${ rgb . join ( ' , ') } )` ;
681734 }
682735
683- return exports . parseKeyword ( val , namedColors ) ;
736+ /**
737+ * <named-color> | <system-color> | currentcolor | transparent
738+ */
739+ const name = exports . parseKeyword ( val , namedColors ) ;
740+ if ( name ) {
741+ return name ;
742+ }
684743} ;
685744
686745/**
0 commit comments