Skip to content

Commit

Permalink
Show color decorators when utility has an opacity modifier in v4 (#969)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
thecrypticace authored May 29, 2024
1 parent b715097 commit bbd7b5b
Show file tree
Hide file tree
Showing 2 changed files with 171 additions and 6 deletions.
128 changes: 124 additions & 4 deletions packages/tailwindcss-language-server/tests/colors/colors.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,68 @@ withFixture('basic', (c) => {
},
],
})

testColors('gradient utilities show colors', {
text: '<div class="from-black from-black/50 via-black via-black/50 to-black to-black/50">',
expected: [
{
range: { start: { line: 0, character: 12 }, end: { line: 0, character: 22 } },
color: {
alpha: 1,
red: 0,
green: 0,
blue: 0,
},
},
{
range: { start: { line: 0, character: 23 }, end: { line: 0, character: 36 } },
color: {
alpha: 0.5,
red: 0,
green: 0,
blue: 0,
},
},

{
range: { start: { line: 0, character: 37 }, end: { line: 0, character: 46 } },
color: {
alpha: 1,
red: 0,
green: 0,
blue: 0,
},
},
{
range: { start: { line: 0, character: 47 }, end: { line: 0, character: 59 } },
color: {
alpha: 0.5,
red: 0,
green: 0,
blue: 0,
},
},

{
range: { start: { line: 0, character: 60 }, end: { line: 0, character: 68 } },
color: {
alpha: 1,
red: 0,
green: 0,
blue: 0,
},
},
{
range: { start: { line: 0, character: 69 }, end: { line: 0, character: 80 } },
color: {
alpha: 0.5,
red: 0,
green: 0,
blue: 0,
},
},
],
})
})

withFixture('v4/basic', (c) => {
Expand Down Expand Up @@ -116,7 +178,6 @@ withFixture('v4/basic', (c) => {
],
})

/*
testColors('opacity modifier', {
text: '<div class="bg-red-500/20">',
expected: [
Expand All @@ -131,7 +192,6 @@ withFixture('v4/basic', (c) => {
},
],
})
*/

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

/*
testColors('arbitrary value and opacity modifier', {
text: '<div class="bg-[red]/[0.5]">',
expected: [
Expand All @@ -163,7 +222,6 @@ withFixture('v4/basic', (c) => {
},
],
})
*/

testColors('oklch colors are parsed', {
text: '<div class="bg-[oklch(60%_0.25_25)]">',
Expand All @@ -179,4 +237,66 @@ withFixture('v4/basic', (c) => {
},
],
})

testColors('gradient utilities show colors', {
text: '<div class="from-black from-black/50 via-black via-black/50 to-black to-black/50">',
expected: [
{
range: { start: { line: 0, character: 12 }, end: { line: 0, character: 22 } },
color: {
alpha: 1,
red: 0,
green: 0,
blue: 0,
},
},
{
range: { start: { line: 0, character: 23 }, end: { line: 0, character: 36 } },
color: {
alpha: 0.5,
red: 0,
green: 0,
blue: 0,
},
},

{
range: { start: { line: 0, character: 37 }, end: { line: 0, character: 46 } },
color: {
alpha: 1,
red: 0,
green: 0,
blue: 0,
},
},
{
range: { start: { line: 0, character: 47 }, end: { line: 0, character: 59 } },
color: {
alpha: 0.5,
red: 0,
green: 0,
blue: 0,
},
},

{
range: { start: { line: 0, character: 60 }, end: { line: 0, character: 68 } },
color: {
alpha: 1,
red: 0,
green: 0,
blue: 0,
},
},
{
range: { start: { line: 0, character: 69 }, end: { line: 0, character: 80 } },
color: {
alpha: 0.5,
red: 0,
green: 0,
blue: 0,
},
},
],
})
})
49 changes: 47 additions & 2 deletions packages/tailwindcss-language-service/src/util/color.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,27 @@ function replaceColorVarsWithTheirDefaults(str: string): string {
return str.replace(/((?:rgba?|hsla?|(?:ok)?(?:lab|lch))\(\s*)var\([^,]+,\s*([^)]+)\)/gi, '$1$2')
}

function replaceHexColorVarsWithTheirDefaults(str: string): string {
// var(--color-red-500, #ef4444)
// -> #ef4444
return str.replace(/var\([^,]+,\s*(#[^)]+)\)/gi, '$1')
}

function getColorsInString(str: string): (culori.Color | KeywordColor)[] {
if (/(?:box|drop)-shadow/.test(str)) return []

return Array.from(replaceColorVarsWithTheirDefaults(str).matchAll(colorRegex), (match) => {
function toColor(match: RegExpMatchArray) {
let color = match[1].replace(/var\([^)]+\)/, '1')
return getKeywordColor(color) ?? culori.parse(color)
}).filter(Boolean)
}

str = replaceHexColorVarsWithTheirDefaults(str)
str = replaceColorVarsWithTheirDefaults(str)
str = removeColorMixWherePossible(str)

let possibleColors = str.matchAll(colorRegex)

return Array.from(possibleColors, toColor).filter(Boolean)
}

function getColorFromDecls(
Expand Down Expand Up @@ -131,6 +145,21 @@ function getColorFromDecls(
}

function getColorFromRoot(state: State, css: postcss.Root): culori.Color | KeywordColor | null {
// Remove any `@property` rules
css = css.clone()
css.walkAtRules((rule) => {
// Ignore declarations inside `@property` rules
if (rule.name === 'property') {
rule.remove()
}

// Ignore declarations @supports (-moz-orient: inline)
// this is a hack used for `@property` fallbacks in Firefox
if (rule.name === 'supports' && rule.params === '(-moz-orient: inline)') {
rule.remove()
}
})

let decls: Record<string, string[]> = {}

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

return culori.formatHex8(color)
}

const COLOR_MIX_REGEX = /color-mix\(in srgb, (.*?) (\d+|\.\d+|\d+\.\d+)%, transparent\)/g

function removeColorMixWherePossible(str: string) {
return str.replace(COLOR_MIX_REGEX, (match, color, percentage) => {
if (color.startsWith('var(')) return match

let parsed = culori.parse(color)
if (!parsed) return match

let alpha = Number(percentage) / 100
if (Number.isNaN(alpha)) return match

return culori.formatRgb({ ...parsed, alpha })
})
}

0 comments on commit bbd7b5b

Please sign in to comment.