Skip to content

Commit bbd7b5b

Browse files
Show color decorators when utility has an opacity modifier in v4 (#969)
* Refactor * Replace color vars with fallback value if hex * Apply alpha-only `color-mix` to color when possible * Re-enable tests * Make sure gradient utilities show color decorators in v4
1 parent b715097 commit bbd7b5b

File tree

2 files changed

+171
-6
lines changed

2 files changed

+171
-6
lines changed

Diff for: packages/tailwindcss-language-server/tests/colors/colors.test.js

+124-4
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,68 @@ withFixture('basic', (c) => {
8787
},
8888
],
8989
})
90+
91+
testColors('gradient utilities show colors', {
92+
text: '<div class="from-black from-black/50 via-black via-black/50 to-black to-black/50">',
93+
expected: [
94+
{
95+
range: { start: { line: 0, character: 12 }, end: { line: 0, character: 22 } },
96+
color: {
97+
alpha: 1,
98+
red: 0,
99+
green: 0,
100+
blue: 0,
101+
},
102+
},
103+
{
104+
range: { start: { line: 0, character: 23 }, end: { line: 0, character: 36 } },
105+
color: {
106+
alpha: 0.5,
107+
red: 0,
108+
green: 0,
109+
blue: 0,
110+
},
111+
},
112+
113+
{
114+
range: { start: { line: 0, character: 37 }, end: { line: 0, character: 46 } },
115+
color: {
116+
alpha: 1,
117+
red: 0,
118+
green: 0,
119+
blue: 0,
120+
},
121+
},
122+
{
123+
range: { start: { line: 0, character: 47 }, end: { line: 0, character: 59 } },
124+
color: {
125+
alpha: 0.5,
126+
red: 0,
127+
green: 0,
128+
blue: 0,
129+
},
130+
},
131+
132+
{
133+
range: { start: { line: 0, character: 60 }, end: { line: 0, character: 68 } },
134+
color: {
135+
alpha: 1,
136+
red: 0,
137+
green: 0,
138+
blue: 0,
139+
},
140+
},
141+
{
142+
range: { start: { line: 0, character: 69 }, end: { line: 0, character: 80 } },
143+
color: {
144+
alpha: 0.5,
145+
red: 0,
146+
green: 0,
147+
blue: 0,
148+
},
149+
},
150+
],
151+
})
90152
})
91153

92154
withFixture('v4/basic', (c) => {
@@ -116,7 +178,6 @@ withFixture('v4/basic', (c) => {
116178
],
117179
})
118180

119-
/*
120181
testColors('opacity modifier', {
121182
text: '<div class="bg-red-500/20">',
122183
expected: [
@@ -131,7 +192,6 @@ withFixture('v4/basic', (c) => {
131192
},
132193
],
133194
})
134-
*/
135195

136196
testColors('arbitrary value', {
137197
text: '<div class="bg-[red]">',
@@ -148,7 +208,6 @@ withFixture('v4/basic', (c) => {
148208
],
149209
})
150210

151-
/*
152211
testColors('arbitrary value and opacity modifier', {
153212
text: '<div class="bg-[red]/[0.5]">',
154213
expected: [
@@ -163,7 +222,6 @@ withFixture('v4/basic', (c) => {
163222
},
164223
],
165224
})
166-
*/
167225

168226
testColors('oklch colors are parsed', {
169227
text: '<div class="bg-[oklch(60%_0.25_25)]">',
@@ -179,4 +237,66 @@ withFixture('v4/basic', (c) => {
179237
},
180238
],
181239
})
240+
241+
testColors('gradient utilities show colors', {
242+
text: '<div class="from-black from-black/50 via-black via-black/50 to-black to-black/50">',
243+
expected: [
244+
{
245+
range: { start: { line: 0, character: 12 }, end: { line: 0, character: 22 } },
246+
color: {
247+
alpha: 1,
248+
red: 0,
249+
green: 0,
250+
blue: 0,
251+
},
252+
},
253+
{
254+
range: { start: { line: 0, character: 23 }, end: { line: 0, character: 36 } },
255+
color: {
256+
alpha: 0.5,
257+
red: 0,
258+
green: 0,
259+
blue: 0,
260+
},
261+
},
262+
263+
{
264+
range: { start: { line: 0, character: 37 }, end: { line: 0, character: 46 } },
265+
color: {
266+
alpha: 1,
267+
red: 0,
268+
green: 0,
269+
blue: 0,
270+
},
271+
},
272+
{
273+
range: { start: { line: 0, character: 47 }, end: { line: 0, character: 59 } },
274+
color: {
275+
alpha: 0.5,
276+
red: 0,
277+
green: 0,
278+
blue: 0,
279+
},
280+
},
281+
282+
{
283+
range: { start: { line: 0, character: 60 }, end: { line: 0, character: 68 } },
284+
color: {
285+
alpha: 1,
286+
red: 0,
287+
green: 0,
288+
blue: 0,
289+
},
290+
},
291+
{
292+
range: { start: { line: 0, character: 69 }, end: { line: 0, character: 80 } },
293+
color: {
294+
alpha: 0.5,
295+
red: 0,
296+
green: 0,
297+
blue: 0,
298+
},
299+
},
300+
],
301+
})
182302
})

Diff for: packages/tailwindcss-language-service/src/util/color.ts

+47-2
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,27 @@ function replaceColorVarsWithTheirDefaults(str: string): string {
5555
return str.replace(/((?:rgba?|hsla?|(?:ok)?(?:lab|lch))\(\s*)var\([^,]+,\s*([^)]+)\)/gi, '$1$2')
5656
}
5757

58+
function replaceHexColorVarsWithTheirDefaults(str: string): string {
59+
// var(--color-red-500, #ef4444)
60+
// -> #ef4444
61+
return str.replace(/var\([^,]+,\s*(#[^)]+)\)/gi, '$1')
62+
}
63+
5864
function getColorsInString(str: string): (culori.Color | KeywordColor)[] {
5965
if (/(?:box|drop)-shadow/.test(str)) return []
6066

61-
return Array.from(replaceColorVarsWithTheirDefaults(str).matchAll(colorRegex), (match) => {
67+
function toColor(match: RegExpMatchArray) {
6268
let color = match[1].replace(/var\([^)]+\)/, '1')
6369
return getKeywordColor(color) ?? culori.parse(color)
64-
}).filter(Boolean)
70+
}
71+
72+
str = replaceHexColorVarsWithTheirDefaults(str)
73+
str = replaceColorVarsWithTheirDefaults(str)
74+
str = removeColorMixWherePossible(str)
75+
76+
let possibleColors = str.matchAll(colorRegex)
77+
78+
return Array.from(possibleColors, toColor).filter(Boolean)
6579
}
6680

6781
function getColorFromDecls(
@@ -131,6 +145,21 @@ function getColorFromDecls(
131145
}
132146

133147
function getColorFromRoot(state: State, css: postcss.Root): culori.Color | KeywordColor | null {
148+
// Remove any `@property` rules
149+
css = css.clone()
150+
css.walkAtRules((rule) => {
151+
// Ignore declarations inside `@property` rules
152+
if (rule.name === 'property') {
153+
rule.remove()
154+
}
155+
156+
// Ignore declarations @supports (-moz-orient: inline)
157+
// this is a hack used for `@property` fallbacks in Firefox
158+
if (rule.name === 'supports' && rule.params === '(-moz-orient: inline)') {
159+
rule.remove()
160+
}
161+
})
162+
134163
let decls: Record<string, string[]> = {}
135164

136165
let rule = postcss.rule({
@@ -238,3 +267,19 @@ export function formatColor(color: culori.Color): string {
238267

239268
return culori.formatHex8(color)
240269
}
270+
271+
const COLOR_MIX_REGEX = /color-mix\(in srgb, (.*?) (\d+|\.\d+|\d+\.\d+)%, transparent\)/g
272+
273+
function removeColorMixWherePossible(str: string) {
274+
return str.replace(COLOR_MIX_REGEX, (match, color, percentage) => {
275+
if (color.startsWith('var(')) return match
276+
277+
let parsed = culori.parse(color)
278+
if (!parsed) return match
279+
280+
let alpha = Number(percentage) / 100
281+
if (Number.isNaN(alpha)) return match
282+
283+
return culori.formatRgb({ ...parsed, alpha })
284+
})
285+
}

0 commit comments

Comments
 (0)