From abcd9acd187e26a326ebb7a45c037741ff7e88d2 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 24 Sep 2021 16:13:16 +0200 Subject: [PATCH] Support URL in arbitrary values (#5587) * add url to resolveArbitraryValue list * add `asURL` data type * add `bg-[url('..')]` regex * allow for `resolveArbitraryValue` to be an array * prevent spaces around `-` when in a `url` * add tests to verify `bg-[url('...')]` and `stroke-[url(...)]` --- src/corePlugins.js | 5 +++-- src/lib/expandTailwindAtRules.js | 2 ++ src/util/createUtilityPlugin.js | 7 +++++-- src/util/pluginUtils.js | 10 ++++++++++ tests/arbitrary-values.test.css | 9 +++++++++ tests/arbitrary-values.test.html | 2 ++ 6 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/corePlugins.js b/src/corePlugins.js index f89f923a6..e801e4624 100644 --- a/src/corePlugins.js +++ b/src/corePlugins.js @@ -17,6 +17,7 @@ import { transformAllClasses, transformLastClasses, asLength, + asURL, asLookupValue, } from './util/pluginUtils' import packageJson from '../package.json' @@ -1361,7 +1362,7 @@ export let backgroundOpacity = createUtilityPlugin('backgroundOpacity', [ export let backgroundImage = createUtilityPlugin( 'backgroundImage', [['bg', ['background-image']]], - { resolveArbitraryValue: asLookupValue } + { resolveArbitraryValue: [asLookupValue, asURL] } ) export let gradientColorStops = (() => { function transparentTo(value) { @@ -1482,7 +1483,7 @@ export let stroke = ({ matchUtilities, theme }) => { } export let strokeWidth = createUtilityPlugin('strokeWidth', [['stroke', ['stroke-width']]], { - resolveArbitraryValue: asLength, + resolveArbitraryValue: [asLength, asURL], }) export let objectFit = ({ addUtilities }) => { diff --git a/src/lib/expandTailwindAtRules.js b/src/lib/expandTailwindAtRules.js index 4ed2c5cf2..3ecdcb3bd 100644 --- a/src/lib/expandTailwindAtRules.js +++ b/src/lib/expandTailwindAtRules.js @@ -7,6 +7,8 @@ let env = sharedState.env let contentMatchCache = sharedState.contentMatchCache const PATTERNS = [ + /([^<>"'`\s]*\[\w*\('[^"'`\s]*'\)\])/.source, // bg-[url('...')] + /([^<>"'`\s]*\[\w*\("[^"'`\s]*"\)\])/.source, // bg-[url("...")] /([^<>"'`\s]*\['[^"'`\s]*'\])/.source, // `content-['hello']` but not `content-['hello']']` /([^<>"'`\s]*\["[^"'`\s]*"\])/.source, // `content-["hello"]` but not `content-["hello"]"]` /([^<>"'`\s]*\[[^"'`\s]+\])/.source, // `fill-[#bada55]` diff --git a/src/util/createUtilityPlugin.js b/src/util/createUtilityPlugin.js index 3fffc390f..4a9dadc5c 100644 --- a/src/util/createUtilityPlugin.js +++ b/src/util/createUtilityPlugin.js @@ -1,11 +1,12 @@ import transformThemeValue from './transformThemeValue' -import { asValue, asColor, asAngle, asLength, asLookupValue } from '../util/pluginUtils' +import { asValue, asColor, asAngle, asLength, asURL, asLookupValue } from '../util/pluginUtils' let asMap = new Map([ [asValue, 'any'], [asColor, 'color'], [asAngle, 'angle'], [asLength, 'length'], + [asURL, 'url'], [asLookupValue, 'lookup'], ]) @@ -38,7 +39,9 @@ export default function createUtilityPlugin( Object.entries(theme(themeKey) ?? {}).filter(([modifier]) => modifier !== 'DEFAULT') ) : theme(themeKey), - type: asMap.get(resolveArbitraryValue) ?? 'any', + type: Array.isArray(resolveArbitraryValue) + ? resolveArbitraryValue.map((typeResolver) => asMap.get(typeResolver) ?? 'any') + : asMap.get(resolveArbitraryValue) ?? 'any', } ) } diff --git a/src/util/pluginUtils.js b/src/util/pluginUtils.js index bdbc3278b..7252314f4 100644 --- a/src/util/pluginUtils.js +++ b/src/util/pluginUtils.js @@ -175,6 +175,9 @@ export function asValue(modifier, lookup = {}, { validate = () => true } = {}) { .replace(/^_/g, ' ') .replace(/\\_/g, '_') + // Keep raw strings if it starts with `url(` + if (value.startsWith('url(')) return value + // add spaces around operators inside calc() that do not follow an operator or ( return value.replace( /(-?\d*\.?\d(?!\b-.+[,)](?![^+\-/*])\D)(?:%|[a-z]+)?|\))([+\-/*])/g, @@ -236,6 +239,12 @@ export function asAngle(modifier, lookup = {}) { return asUnit(modifier, ['deg', 'grad', 'rad', 'turn'], lookup) } +export function asURL(modifier, lookup = {}) { + return asValue(modifier, lookup, { + validate: (value) => value.startsWith('url('), + }) +} + export function asLength(modifier, lookup = {}) { return asUnit( modifier, @@ -271,6 +280,7 @@ let typeMap = { color: asColor, angle: asAngle, length: asLength, + url: asURL, lookup: asLookupValue, } diff --git a/tests/arbitrary-values.test.css b/tests/arbitrary-values.test.css index 2b81c7820..6d915765d 100644 --- a/tests/arbitrary-values.test.css +++ b/tests/arbitrary-values.test.css @@ -275,6 +275,12 @@ .bg-opacity-\[var\(--value\)\] { --tw-bg-opacity: var(--value); } +.bg-\[url\(\'\/path-to-image\.png\'\)\] { + background-image: url('/path-to-image.png'); +} +.bg-\[url\:var\(--url\)\] { + background-image: var(--url); +} .from-\[\#da5b66\] { --tw-gradient-from: #da5b66; --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to, rgb(218 91 102 / 0)); @@ -305,6 +311,9 @@ .stroke-\[\#da5b66\] { stroke: #da5b66; } +.stroke-\[url\(\#icon-gradient\)\] { + stroke-width: url(#icon-gradient); +} .object-\[50\%\2c 50\%\] { object-position: 50% 50%; } diff --git a/tests/arbitrary-values.test.html b/tests/arbitrary-values.test.html index 505f96bd8..bab407ed8 100644 --- a/tests/arbitrary-values.test.html +++ b/tests/arbitrary-values.test.html @@ -11,6 +11,7 @@
+
@@ -116,6 +117,7 @@
+