From 501ebfcefdd203cfe6588cbf4b443cdc1e4fea86 Mon Sep 17 00:00:00 2001
From: Brad Cornes <hello@bradley.dev>
Date: Tue, 26 Apr 2022 12:17:00 +0100
Subject: [PATCH] Improve type detection for arbitrary color values

---
 src/util/color.js              | 38 ++++++++++++++++++----------------
 src/util/dataTypes.js          |  2 +-
 tests/arbitrary-values.test.js | 10 +++++++++
 3 files changed, 31 insertions(+), 19 deletions(-)

diff --git a/src/util/color.js b/src/util/color.js
index da8ec7bc3a06..8ba94556f958 100644
--- a/src/util/color.js
+++ b/src/util/color.js
@@ -8,13 +8,15 @@ let ALPHA_SEP = /\s*[,/]\s*/
 let CUSTOM_PROPERTY = /var\(--(?:[^ )]*?)\)/
 
 let RGB = new RegExp(
-  `^rgba?\\(\\s*(${VALUE.source}|${CUSTOM_PROPERTY.source})${SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source})${SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source})(?:${ALPHA_SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?\\s*\\)$`
+  `^(rgb)a?\\(\\s*(${VALUE.source}|${CUSTOM_PROPERTY.source})(?:${SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?(?:${SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?(?:${ALPHA_SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?\\s*\\)$`
 )
 let HSL = new RegExp(
-  `^hsla?\\(\\s*((?:${VALUE.source})(?:deg|rad|grad|turn)?|${CUSTOM_PROPERTY.source})${SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source})${SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source})(?:${ALPHA_SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?\\s*\\)$`
+  `^(hsl)a?\\(\\s*((?:${VALUE.source})(?:deg|rad|grad|turn)?|${CUSTOM_PROPERTY.source})(?:${SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?(?:${SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?(?:${ALPHA_SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?\\s*\\)$`
 )
 
-export function parseColor(value) {
+// In "loose" mode the color may contain fewer than 3 parts, as long as at least
+// one of the parts is variable.
+export function parseColor(value, { loose = false } = {}) {
   if (typeof value !== 'string') {
     return null
   }
@@ -42,27 +44,27 @@ export function parseColor(value) {
     }
   }
 
-  let rgbMatch = value.match(RGB)
+  let match = value.match(RGB) ?? value.match(HSL)
 
-  if (rgbMatch !== null) {
-    return {
-      mode: 'rgb',
-      color: [rgbMatch[1], rgbMatch[2], rgbMatch[3]].map((v) => v.toString()),
-      alpha: rgbMatch[4]?.toString?.(),
-    }
+  if (match === null) {
+    return null
   }
 
-  let hslMatch = value.match(HSL)
+  let color = [match[2], match[3], match[4]].filter(Boolean).map((v) => v.toString())
 
-  if (hslMatch !== null) {
-    return {
-      mode: 'hsl',
-      color: [hslMatch[1], hslMatch[2], hslMatch[3]].map((v) => v.toString()),
-      alpha: hslMatch[4]?.toString?.(),
-    }
+  if (!loose && color.length !== 3) {
+    return null
   }
 
-  return null
+  if (color.length < 3 && !color.some((part) => /^var\(.*?\)$/.test(part))) {
+    return null
+  }
+
+  return {
+    mode: match[1],
+    color,
+    alpha: match[5]?.toString?.(),
+  }
 }
 
 export function formatColor({ mode, color, alpha }) {
diff --git a/src/util/dataTypes.js b/src/util/dataTypes.js
index 61b4ed732695..04a5ee901aa7 100644
--- a/src/util/dataTypes.js
+++ b/src/util/dataTypes.js
@@ -121,7 +121,7 @@ export function color(value) {
     part = normalize(part)
 
     if (part.startsWith('var(')) return true
-    if (parseColor(part) !== null) return colors++, true
+    if (parseColor(part, { loose: true }) !== null) return colors++, true
 
     return false
   })
diff --git a/tests/arbitrary-values.test.js b/tests/arbitrary-values.test.js
index ab643ed3159f..baea4ddef9c5 100644
--- a/tests/arbitrary-values.test.js
+++ b/tests/arbitrary-values.test.js
@@ -68,6 +68,8 @@ it('should support arbitrary values for various background utilities', () => {
           <!-- By implicit type -->
           <div class="bg-[url('/image-1-0.png')]"></div>
           <div class="bg-[#ff0000]"></div>
+          <div class="bg-[rgb(var(--bg-color))]"></div>
+          <div class="bg-[hsl(var(--bg-color))]"></div>
 
           <!-- By explicit type -->
           <div class="bg-[url:var(--image-url)]"></div>
@@ -89,6 +91,14 @@ it('should support arbitrary values for various background utilities', () => {
         background-color: rgb(255 0 0 / var(--tw-bg-opacity));
       }
 
+      .bg-\[rgb\(var\(--bg-color\)\)\] {
+        background-color: rgb(var(--bg-color));
+      }
+
+      .bg-\[hsl\(var\(--bg-color\)\)\] {
+        background-color: hsl(var(--bg-color));
+      }
+
       .bg-\[color\:var\(--bg-color\)\] {
         background-color: var(--bg-color);
       }