@@ -3,6 +3,77 @@ import { segment } from './segment'
33const KEYWORDS = new Set ( [ 'inset' , 'inherit' , 'initial' , 'revert' , 'unset' ] )
44const LENGTH = / ^ - ? ( \d + | \. \d + ) ( .* ?) $ / g
55
6+ /**
7+ * Extract the alpha channel from a color value.
8+ * Returns the alpha as a percentage string (e.g., "12%") or null if no alpha is found.
9+ */
10+ function extractAlpha ( color : string ) : string | null {
11+ // Modern rgba/hsla syntax with slash: rgba(0 0 0 / 0.12) or rgba(0 0 0 / 12%)
12+ const slashAlphaMatch = color . match ( / \/ \s * ( [ \d . ] + ) % ? \s * \) / )
13+ if ( slashAlphaMatch ) {
14+ let alpha = slashAlphaMatch [ 1 ]
15+ // If it's already a percentage, return it
16+ if ( slashAlphaMatch [ 0 ] . includes ( '%' ) ) {
17+ return `${ alpha } %`
18+ }
19+ // Convert decimal to percentage
20+ const alphaNum = parseFloat ( alpha )
21+ if ( ! isNaN ( alphaNum ) ) {
22+ // Round to avoid floating point precision issues
23+ return `${ Math . round ( alphaNum * 100 ) } %`
24+ }
25+ return null
26+ }
27+
28+ // Legacy rgba/hsla syntax with comma: rgba(0, 0, 0, 0.12)
29+ const commaAlphaMatch = color . match ( / , \s * ( [ \d . ] + ) \s * \) / )
30+ if ( commaAlphaMatch ) {
31+ const alpha = commaAlphaMatch [ 1 ]
32+ const alphaNum = parseFloat ( alpha )
33+ if ( ! isNaN ( alphaNum ) ) {
34+ // Round to avoid floating point precision issues
35+ return `${ Math . round ( alphaNum * 100 ) } %`
36+ }
37+ return null
38+ }
39+
40+ // No alpha found
41+ return null
42+ }
43+
44+ /**
45+ * Extract the base color without the alpha channel.
46+ * For rgba/hsla, returns rgb/hsl with the same values but without alpha.
47+ * For other colors, returns as-is.
48+ */
49+ function stripAlpha ( color : string ) : string {
50+ // Modern rgba/hsla syntax with slash: rgba(0 0 0 / 0.12) or hsla(0 0% 0% / 0.3)
51+ const slashMatch = color . match ( / ^ ( r g b a ? | h s l a ? ) \( ( [ \d \s . % ] + ) \s * \/ \s * [ \d . ] + % ? \s * \) $ / i)
52+ if ( slashMatch ) {
53+ const type = slashMatch [ 1 ] . toLowerCase ( ) . replace ( 'a' , '' )
54+ const values = slashMatch [ 2 ] . trim ( )
55+ return `${ type } (${ values } )`
56+ }
57+
58+ // Legacy rgba/hsla syntax with comma: rgba(0, 0, 0, 0.12) or hsla(0, 0%, 0%, 0.3)
59+ const commaMatch = color . match ( / ^ ( r g b a ? | h s l a ? ) \( ( [ \d \s . , % ] + ) , \s * [ \d . ] + \s * \) $ / i)
60+ if ( commaMatch ) {
61+ const type = commaMatch [ 1 ] . toLowerCase ( ) . replace ( 'a' , '' )
62+ const values = commaMatch [ 2 ] . trim ( )
63+ return `${ type } (${ values } )`
64+ }
65+
66+ // No alpha to strip
67+ return color
68+ }
69+
70+ /**
71+ * Check if a string already contains alpha handling (oklab, color-mix, etc.)
72+ */
73+ function hasAlphaHandling ( value : string ) : boolean {
74+ return value . includes ( 'oklab(' ) || value . includes ( 'color-mix(' ) || value . includes ( 'oklch(' )
75+ }
76+
677export function replaceShadowColors ( input : string , replacement : ( color : string ) => string ) {
778 let shadows = segment ( input , ',' ) . map ( ( shadow ) => {
879 shadow = shadow . trim ( )
@@ -33,7 +104,25 @@ export function replaceShadowColors(input: string, replacement: (color: string)
33104 // we can't know what to replace.
34105 if ( offsetX === null || offsetY === null ) return shadow
35106
36- let replacementColor = replacement ( color ?? 'currentcolor' )
107+ // Extract alpha from the original color if present
108+ let alpha : string | null = null
109+ let baseColor : string = color ?? 'currentcolor'
110+
111+ if ( color ) {
112+ alpha = extractAlpha ( color )
113+ if ( alpha ) {
114+ baseColor = stripAlpha ( color )
115+ }
116+ }
117+
118+ let replacementColor = replacement ( baseColor )
119+
120+ // Only apply color-mix wrapping if:
121+ // 1. The original color had an alpha channel
122+ // 2. The replacement doesn't already have alpha handling (oklab, color-mix, etc.)
123+ if ( alpha && ! hasAlphaHandling ( replacementColor ) ) {
124+ replacementColor = `color-mix(in srgb, transparent, ${ replacementColor } ${ alpha } )`
125+ }
37126
38127 if ( color !== null ) {
39128 // If a color was found, replace the color.
0 commit comments