From 79b37a874c082189645663f14bb478571de71931 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 24 Sep 2021 18:18:48 +0200 Subject: [PATCH 01/11] Ensure invalid typehints are not generated (#5590) --- src/util/pluginUtils.js | 12 ++++++++++-- tests/arbitrary-values.test.js | 14 ++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/util/pluginUtils.js b/src/util/pluginUtils.js index 7252314f47f9..ddd499002445 100644 --- a/src/util/pluginUtils.js +++ b/src/util/pluginUtils.js @@ -284,8 +284,12 @@ let typeMap = { lookup: asLookupValue, } +let supportedTypes = Object.keys(typeMap) + function splitAtFirst(input, delim) { - return (([first, ...rest]) => [first, rest.join(delim)])(input.split(delim)) + let idx = input.indexOf(delim) + if (idx === -1) return [undefined, input] + return [input.slice(0, idx), input.slice(idx + 1)] } export function coerceValue(type, modifier, values, tailwindConfig) { @@ -294,7 +298,11 @@ export function coerceValue(type, modifier, values, tailwindConfig) { if (isArbitraryValue(modifier)) { let [explicitType, value] = splitAtFirst(modifier.slice(1, -1), ':') - if (value.length > 0 && Object.keys(typeMap).includes(explicitType)) { + if (explicitType !== undefined && !supportedTypes.includes(explicitType)) { + return [] + } + + if (value.length > 0 && supportedTypes.includes(explicitType)) { return [asValue(`[${value}]`, values, tailwindConfig), explicitType] } diff --git a/tests/arbitrary-values.test.js b/tests/arbitrary-values.test.js index 2472d7a773d3..e223ddebe5d9 100644 --- a/tests/arbitrary-values.test.js +++ b/tests/arbitrary-values.test.js @@ -16,6 +16,20 @@ test('arbitrary values', () => { }) }) +it('should not generate any css if an unknown typehint is used', () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + } + + return run('@tailwind utilities', config).then((result) => { + return expect(result.css).toMatchFormattedCss(css``) + }) +}) + it('should convert _ to spaces', () => { let config = { content: [ From ab17c6c42771134e4a3b3fa8c7b51e1979748b64 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 24 Sep 2021 18:28:08 +0200 Subject: [PATCH 02/11] Handle unknown typehints (#5588) If you use a typehint like `w-[length:12px]`, then currently that wouldn't generate anything because we don't have a matchUtilities for `w` with a `length` type. To fix this, we can detect if this is unnecessary, if it is we still generate the expected outcome. (In this case `width: 12px`) but we also warn to the user that we detected this. Currently we detect this by checking if there is only a single plugin registered for handling the prefix (e.g.: `w-`). We can probably improve this by also checking all the types that all plugins are handling for the resolved plugins. --- src/lib/generateRules.js | 3 ++- src/lib/setupContextUtils.js | 18 ++++++++++++++++-- tests/arbitrary-values.test.js | 12 ++++++++++++ 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/lib/generateRules.js b/src/lib/generateRules.js index 53107b966dac..265e2482e0e8 100644 --- a/src/lib/generateRules.js +++ b/src/lib/generateRules.js @@ -226,10 +226,11 @@ function* resolveMatches(candidate, context) { for (let matchedPlugins of resolveMatchedPlugins(classCandidate, context)) { let matches = [] let [plugins, modifier] = matchedPlugins + let isOnlyPlugin = plugins.length === 1 for (let [sort, plugin] of plugins) { if (typeof plugin === 'function') { - for (let ruleSet of [].concat(plugin(modifier))) { + for (let ruleSet of [].concat(plugin(modifier, { isOnlyPlugin }))) { let [rules, options] = parseRules(ruleSet, context.postCssNodeCache) for (let rule of rules) { matches.push([{ ...sort, options: { ...sort.options, ...options } }, rule]) diff --git a/src/lib/setupContextUtils.js b/src/lib/setupContextUtils.js index ed7c974d4940..ba643fce46df 100644 --- a/src/lib/setupContextUtils.js +++ b/src/lib/setupContextUtils.js @@ -300,15 +300,29 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs classList.add([prefixedIdentifier, options]) - function wrapped(modifier) { + function wrapped(modifier, { isOnlyPlugin }) { let { type = 'any' } = options type = [].concat(type) let [value, coercedType] = coerceValue(type, modifier, options.values, tailwindConfig) - if (!type.includes(coercedType) || value === undefined) { + if (value === undefined) { return [] } + if (!type.includes(coercedType)) { + if (isOnlyPlugin) { + log.warn([ + `Unnecessary typehint \`${coercedType}\` in \`${identifier}-${modifier}\`.`, + `You can safely update it to \`${identifier}-${modifier.replace( + coercedType + ':', + '' + )}\`.`, + ]) + } else { + return [] + } + } + if (!isValidArbitraryValue(value)) { return [] } diff --git a/tests/arbitrary-values.test.js b/tests/arbitrary-values.test.js index e223ddebe5d9..17252be92ef9 100644 --- a/tests/arbitrary-values.test.js +++ b/tests/arbitrary-values.test.js @@ -30,6 +30,18 @@ it('should not generate any css if an unknown typehint is used', () => { }) }) +it('should handle unknown typehints', () => { + let config = { content: [{ raw: html`
` }] } + + return run('@tailwind utilities', config).then((result) => { + return expect(result.css).toMatchFormattedCss(` + .w-\\[length\\:12px\\] { + width: 12px; + } + `) + }) +}) + it('should convert _ to spaces', () => { let config = { content: [ From 87da0a1266646a6b88808b0d37bebbebdb32670b Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 24 Sep 2021 16:55:24 +0200 Subject: [PATCH 03/11] simplify `inset` plugin --- src/corePlugins.js | 42 +++++++++++++----------------------------- 1 file changed, 13 insertions(+), 29 deletions(-) diff --git a/src/corePlugins.js b/src/corePlugins.js index e801e4624e68..930b5433bb2e 100644 --- a/src/corePlugins.js +++ b/src/corePlugins.js @@ -520,35 +520,19 @@ export let position = ({ addUtilities }) => { }) } -export let inset = ({ matchUtilities, theme }) => { - let options = { - values: theme('inset'), - type: 'any', - } - - matchUtilities( - { inset: (value) => ({ top: value, right: value, bottom: value, left: value }) }, - options - ) - - matchUtilities( - { - 'inset-x': (value) => ({ left: value, right: value }), - 'inset-y': (value) => ({ top: value, bottom: value }), - }, - options - ) - - matchUtilities( - { - top: (top) => ({ top }), - right: (right) => ({ right }), - bottom: (bottom) => ({ bottom }), - left: (left) => ({ left }), - }, - options - ) -} +export let inset = createUtilityPlugin('inset', [ + ['inset', ['top', 'right', 'bottom', 'left']], + [ + ['inset-x', ['left', 'right']], + ['inset-y', ['top', 'bottom']], + ], + [ + ['top', ['top']], + ['right', ['right']], + ['bottom', ['bottom']], + ['left', ['left']], + ], +]) export let isolation = ({ addUtilities }) => { addUtilities({ From a45967d9ddf4c0207d48a9c8b1a9113641f61503 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 24 Sep 2021 16:56:49 +0200 Subject: [PATCH 04/11] run `prettier` on stub file --- stubs/defaultConfig.stub.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/stubs/defaultConfig.stub.js b/stubs/defaultConfig.stub.js index 3ce4b4e5504e..98f7fc9b699c 100644 --- a/stubs/defaultConfig.stub.js +++ b/stubs/defaultConfig.stub.js @@ -111,9 +111,9 @@ module.exports = { bounce: 'bounce 1s infinite', }, aspectRatio: { - 'auto': 'auto', - 'square': '1 / 1', - 'video': '16 / 9', + auto: 'auto', + square: '1 / 1', + video: '16 / 9', }, backdropBlur: (theme) => theme('blur'), backdropBrightness: (theme) => theme('brightness'), @@ -847,10 +847,10 @@ module.exports = { max: 'max-content', }), willChange: { - 'auto': 'auto', - 'scroll': 'scroll-position', - 'contents': 'contents', - 'transform': 'transform', + auto: 'auto', + scroll: 'scroll-position', + contents: 'contents', + transform: 'transform', }, zIndex: { auto: 'auto', From 640026ca215dc779601b74342f259383d330cfe9 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 24 Sep 2021 16:58:08 +0200 Subject: [PATCH 05/11] simplify `align` utility --- src/corePlugins.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/corePlugins.js b/src/corePlugins.js index 930b5433bb2e..e832367e0f1f 100644 --- a/src/corePlugins.js +++ b/src/corePlugins.js @@ -1518,14 +1518,7 @@ export let verticalAlign = ({ addUtilities, matchUtilities }) => { '.align-super': { 'vertical-align': 'super' }, }) - matchUtilities( - { - align: (value) => ({ - 'vertical-align': value, - }), - }, - { values: {}, type: 'any' } - ) + matchUtilities({ align: (value) => ({ 'vertical-align': value }) }) } export let fontFamily = createUtilityPlugin('fontFamily', [['font', ['fontFamily']]], { From 5f45020036ea957e12c4f5e49dde036692f38157 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 24 Sep 2021 16:59:38 +0200 Subject: [PATCH 06/11] improve arbitrary support for outline This will allow us to use `outline-[OUTLINE,OPTIONAL_OFFSET]` Input: ```html outline-[2px_solid_black] ``` Output: ```css .outline-\[2px_solid_black\] { outline: 2px solid black; outline-offset: 0; } ``` --- Input: ```html outline-[2px_solid_black,2px] ``` Output: ```css .outline-\[2px_solid_black\2c 2px\] { outline: 2px solid black; outline-offset: 2px; } ``` --- src/corePlugins.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/corePlugins.js b/src/corePlugins.js index e832367e0f1f..e82341f5df30 100644 --- a/src/corePlugins.js +++ b/src/corePlugins.js @@ -1776,6 +1776,7 @@ export let outline = ({ matchUtilities, theme }) => { matchUtilities( { outline: (value) => { + value = Array.isArray(value) ? value : value.split(',') let [outline, outlineOffset = '0'] = Array.isArray(value) ? value : [value] return { outline, 'outline-offset': outlineOffset } From 0ca42fe1ddace8828f7187ae45e4a6b5c33c4dff Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 24 Sep 2021 17:00:55 +0200 Subject: [PATCH 07/11] remove default `type` --- src/corePlugins.js | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/corePlugins.js b/src/corePlugins.js index e82341f5df30..aea8c0c8bd32 100644 --- a/src/corePlugins.js +++ b/src/corePlugins.js @@ -976,7 +976,7 @@ export let space = ({ matchUtilities, addUtilities, theme }) => { } }, }, - { values: theme('space'), type: 'any' } + { values: theme('space') } ) addUtilities({ @@ -1073,7 +1073,7 @@ export let divideOpacity = ({ matchUtilities, theme }) => { return { [`& > :not([hidden]) ~ :not([hidden])`]: { '--tw-divide-opacity': value } } }, }, - { values: theme('divideOpacity'), type: 'any' } + { values: theme('divideOpacity') } ) } @@ -1668,7 +1668,7 @@ export let placeholderOpacity = ({ matchUtilities, theme }) => { return { ['&::placeholder']: { '--tw-placeholder-opacity': value } } }, }, - { values: theme('placeholderOpacity'), type: 'any' } + { values: theme('placeholderOpacity') } ) } @@ -1782,7 +1782,7 @@ export let outline = ({ matchUtilities, theme }) => { return { outline, 'outline-offset': outlineOffset } }, }, - { values: theme('outline'), type: 'any' } + { values: theme('outline') } ) } @@ -1886,7 +1886,7 @@ export let blur = ({ matchUtilities, theme }) => { } }, }, - { values: theme('blur'), type: 'any' } + { values: theme('blur') } ) } @@ -1901,7 +1901,7 @@ export let brightness = ({ matchUtilities, theme }) => { } }, }, - { values: theme('brightness'), type: 'any' } + { values: theme('brightness') } ) } @@ -1916,7 +1916,7 @@ export let contrast = ({ matchUtilities, theme }) => { } }, }, - { values: theme('contrast'), type: 'any' } + { values: theme('contrast') } ) } @@ -1948,7 +1948,7 @@ export let grayscale = ({ matchUtilities, theme }) => { } }, }, - { values: theme('grayscale'), type: 'any' } + { values: theme('grayscale') } ) } @@ -1963,7 +1963,7 @@ export let hueRotate = ({ matchUtilities, theme }) => { } }, }, - { values: theme('hueRotate'), type: 'any' } + { values: theme('hueRotate') } ) } @@ -1978,7 +1978,7 @@ export let invert = ({ matchUtilities, theme }) => { } }, }, - { values: theme('invert'), type: 'any' } + { values: theme('invert') } ) } @@ -1993,7 +1993,7 @@ export let saturate = ({ matchUtilities, theme }) => { } }, }, - { values: theme('saturate'), type: 'any' } + { values: theme('saturate') } ) } @@ -2008,7 +2008,7 @@ export let sepia = ({ matchUtilities, theme }) => { } }, }, - { values: theme('sepia'), type: 'any' } + { values: theme('sepia') } ) } @@ -2054,7 +2054,7 @@ export let backdropBlur = ({ matchUtilities, theme }) => { } }, }, - { values: theme('backdropBlur'), type: 'any' } + { values: theme('backdropBlur') } ) } @@ -2069,7 +2069,7 @@ export let backdropBrightness = ({ matchUtilities, theme }) => { } }, }, - { values: theme('backdropBrightness'), type: 'any' } + { values: theme('backdropBrightness') } ) } @@ -2084,7 +2084,7 @@ export let backdropContrast = ({ matchUtilities, theme }) => { } }, }, - { values: theme('backdropContrast'), type: 'any' } + { values: theme('backdropContrast') } ) } @@ -2099,7 +2099,7 @@ export let backdropGrayscale = ({ matchUtilities, theme }) => { } }, }, - { values: theme('backdropGrayscale'), type: 'any' } + { values: theme('backdropGrayscale') } ) } @@ -2114,7 +2114,7 @@ export let backdropHueRotate = ({ matchUtilities, theme }) => { } }, }, - { values: theme('backdropHueRotate'), type: 'any' } + { values: theme('backdropHueRotate') } ) } @@ -2129,7 +2129,7 @@ export let backdropInvert = ({ matchUtilities, theme }) => { } }, }, - { values: theme('backdropInvert'), type: 'any' } + { values: theme('backdropInvert') } ) } @@ -2144,7 +2144,7 @@ export let backdropOpacity = ({ matchUtilities, theme }) => { } }, }, - { values: theme('backdropOpacity'), type: 'any' } + { values: theme('backdropOpacity') } ) } @@ -2159,7 +2159,7 @@ export let backdropSaturate = ({ matchUtilities, theme }) => { } }, }, - { values: theme('backdropSaturate'), type: 'any' } + { values: theme('backdropSaturate') } ) } @@ -2174,7 +2174,7 @@ export let backdropSepia = ({ matchUtilities, theme }) => { } }, }, - { values: theme('backdropSepia'), type: 'any' } + { values: theme('backdropSepia') } ) } @@ -2230,7 +2230,7 @@ export let transitionProperty = ({ matchUtilities, theme }) => { } }, }, - { values: theme('transitionProperty'), type: 'any' } + { values: theme('transitionProperty') } ) } From 05e4099e2dfbf395669b9cf12b86966b255838e4 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 24 Sep 2021 17:02:44 +0200 Subject: [PATCH 08/11] simplify createUtilityPlugin, use types directly --- src/util/createUtilityPlugin.js | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/src/util/createUtilityPlugin.js b/src/util/createUtilityPlugin.js index 4a9dadc5c790..1760a05cdce7 100644 --- a/src/util/createUtilityPlugin.js +++ b/src/util/createUtilityPlugin.js @@ -1,19 +1,9 @@ import transformThemeValue from './transformThemeValue' -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'], -]) export default function createUtilityPlugin( themeKey, utilityVariations = [[themeKey, [themeKey]]], - { filterDefault = false, resolveArbitraryValue = asValue } = {} + { filterDefault = false, type = 'any' } = {} ) { let transformValue = transformThemeValue(themeKey) return function ({ matchUtilities, theme }) { @@ -39,9 +29,7 @@ export default function createUtilityPlugin( Object.entries(theme(themeKey) ?? {}).filter(([modifier]) => modifier !== 'DEFAULT') ) : theme(themeKey), - type: Array.isArray(resolveArbitraryValue) - ? resolveArbitraryValue.map((typeResolver) => asMap.get(typeResolver) ?? 'any') - : asMap.get(resolveArbitraryValue) ?? 'any', + type, } ) } From 355371f0cb8c3665b7e346784667e807f82b8406 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 24 Sep 2021 17:13:27 +0200 Subject: [PATCH 09/11] find first matching type when coercing the value --- src/util/pluginUtils.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/util/pluginUtils.js b/src/util/pluginUtils.js index ddd499002445..6dc02bb5cde2 100644 --- a/src/util/pluginUtils.js +++ b/src/util/pluginUtils.js @@ -292,9 +292,7 @@ function splitAtFirst(input, delim) { return [input.slice(0, idx), input.slice(idx + 1)] } -export function coerceValue(type, modifier, values, tailwindConfig) { - let [scaleType, arbitraryType = scaleType] = [].concat(type) - +export function coerceValue(types, modifier, values, tailwindConfig) { if (isArbitraryValue(modifier)) { let [explicitType, value] = splitAtFirst(modifier.slice(1, -1), ':') @@ -305,9 +303,13 @@ export function coerceValue(type, modifier, values, tailwindConfig) { if (value.length > 0 && supportedTypes.includes(explicitType)) { return [asValue(`[${value}]`, values, tailwindConfig), explicitType] } + } - return [typeMap[arbitraryType](modifier, values, tailwindConfig), arbitraryType] + // Find first matching type + for (let type of [].concat(types)) { + let result = typeMap[type](modifier, values, tailwindConfig) + if (result) return [result, type] } - return [typeMap[scaleType](modifier, values, tailwindConfig), scaleType] + return [] } From d07a3e9cd8852e4734ad4eec2aa5b9c123dfbd46 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 24 Sep 2021 17:14:17 +0200 Subject: [PATCH 10/11] introduce css data types Ref: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Types These data types will be used to "guess" the type of an arbitrary value if there is some ambiguity going on. For example: ``` bg-[#0088cc] -> This is a `color` -> `background-color` bg-[url('...')] -> This is a `url` -> `background-image` ``` If you are using css variables, then there is no way of knowing which type it is referring to, in that case you can be explicit: ``` bg-[color:var(--value)] -> This is a `color` -> `background-color` bg-[url:var(--value)] -> This is a `url` -> `background-image` ``` When you explicitly pass a data type, then we bypass the type system and assume you are right. This is nice in a way because now we don't have to run all of the guessing type code. On the other hand, you can introduce runtime issues that we are not able to detect: ``` :root { --value: 12px; } /* Later... */ bg-[color:var(--value)] -> Assumes `color` -> *eventually* -> `background-color: 12px` ``` --- src/corePlugins.js | 30 ++--- src/lib/expandTailwindAtRules.js | 2 + src/util/dataTypes.js | 216 +++++++++++++++++++++++++++++++ src/util/pluginUtils.js | 105 +++++---------- 4 files changed, 268 insertions(+), 85 deletions(-) create mode 100644 src/util/dataTypes.js diff --git a/src/corePlugins.js b/src/corePlugins.js index aea8c0c8bd32..e8dccc8b5e25 100644 --- a/src/corePlugins.js +++ b/src/corePlugins.js @@ -16,9 +16,6 @@ import { transformAllSelectors, transformAllClasses, transformLastClasses, - asLength, - asURL, - asLookupValue, } from './util/pluginUtils' import packageJson from '../package.json' import log from './util/log' @@ -617,12 +614,15 @@ export let display = ({ addUtilities }) => { } export let aspectRatio = createUtilityPlugin('aspectRatio', [['aspect', ['aspect-ratio']]]) + export let height = createUtilityPlugin('height', [['h', ['height']]]) export let maxHeight = createUtilityPlugin('maxHeight', [['max-h', ['maxHeight']]]) export let minHeight = createUtilityPlugin('minHeight', [['min-h', ['minHeight']]]) + export let width = createUtilityPlugin('width', [['w', ['width']]]) export let minWidth = createUtilityPlugin('minWidth', [['min-w', ['minWidth']]]) export let maxWidth = createUtilityPlugin('maxWidth', [['max-w', ['maxWidth']]]) + export let flex = createUtilityPlugin('flex') export let flexShrink = createUtilityPlugin('flexShrink', [['flex-shrink', ['flex-shrink']]]) export let flexGrow = createUtilityPlugin('flexGrow', [['flex-grow', ['flex-grow']]]) @@ -1013,7 +1013,7 @@ export let divideWidth = ({ matchUtilities, addUtilities, theme }) => { } }, }, - { values: theme('divideWidth'), type: 'length' } + { values: theme('divideWidth'), type: ['line-width', 'length'] } ) addUtilities({ @@ -1199,7 +1199,7 @@ export let borderWidth = createUtilityPlugin( ['border-l', [['@defaults border-width', {}], 'border-left-width']], ], ], - { resolveArbitraryValue: asLength } + { type: ['line-width', 'length'] } ) export let borderStyle = ({ addUtilities }) => { @@ -1249,7 +1249,7 @@ export let borderColor = ({ addBase, matchUtilities, theme, corePlugins }) => { }, { values: (({ DEFAULT: _, ...colors }) => colors)(flattenColorPalette(theme('borderColor'))), - type: 'color', + type: ['color'], } ) @@ -1346,7 +1346,7 @@ export let backgroundOpacity = createUtilityPlugin('backgroundOpacity', [ export let backgroundImage = createUtilityPlugin( 'backgroundImage', [['bg', ['background-image']]], - { resolveArbitraryValue: [asLookupValue, asURL] } + { type: ['lookup', 'image', 'url'] } ) export let gradientColorStops = (() => { function transparentTo(value) { @@ -1399,7 +1399,7 @@ export let boxDecorationBreak = ({ addUtilities }) => { } export let backgroundSize = createUtilityPlugin('backgroundSize', [['bg', ['background-size']]], { - resolveArbitraryValue: asLookupValue, + type: ['lookup', 'length', 'percentage'], }) export let backgroundAttachment = ({ addUtilities }) => { @@ -1422,7 +1422,7 @@ export let backgroundClip = ({ addUtilities }) => { export let backgroundPosition = createUtilityPlugin( 'backgroundPosition', [['bg', ['background-position']]], - { resolveArbitraryValue: asLookupValue } + { type: ['lookup', 'position'] } ) export let backgroundRepeat = ({ addUtilities }) => { @@ -1462,12 +1462,12 @@ export let stroke = ({ matchUtilities, theme }) => { return { stroke: toColorValue(value) } }, }, - { values: flattenColorPalette(theme('stroke')), type: 'color' } + { values: flattenColorPalette(theme('stroke')), type: ['color', 'url'] } ) } export let strokeWidth = createUtilityPlugin('strokeWidth', [['stroke', ['stroke-width']]], { - resolveArbitraryValue: [asLength, asURL], + type: ['length', 'number', 'percentage'], }) export let objectFit = ({ addUtilities }) => { @@ -1522,7 +1522,7 @@ export let verticalAlign = ({ addUtilities, matchUtilities }) => { } export let fontFamily = createUtilityPlugin('fontFamily', [['font', ['fontFamily']]], { - resolveArbitraryValue: asLookupValue, + type: ['lookup', 'generic-name', 'family-name'], }) export let fontSize = ({ matchUtilities, theme }) => { @@ -1541,12 +1541,12 @@ export let fontSize = ({ matchUtilities, theme }) => { } }, }, - { values: theme('fontSize'), type: 'length' } + { values: theme('fontSize'), type: ['absolute-size', 'relative-size', 'length', 'percentage'] } ) } export let fontWeight = createUtilityPlugin('fontWeight', [['font', ['fontWeight']]], { - resolveArbitraryValue: asLookupValue, + type: ['lookup', 'number'], }) export let textTransform = ({ addUtilities }) => { @@ -1859,7 +1859,7 @@ export let ringOpacity = createUtilityPlugin( export let ringOffsetWidth = createUtilityPlugin( 'ringOffsetWidth', [['ring-offset', ['--tw-ring-offset-width']]], - { resolveArbitraryValue: asLength } + { type: 'length' } ) export let ringOffsetColor = ({ matchUtilities, theme }) => { diff --git a/src/lib/expandTailwindAtRules.js b/src/lib/expandTailwindAtRules.js index 3ecdcb3bd46a..bdc8e68d4a92 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, // font-['some_font',sans-serif] + /([^<>"'`\s]*\[\w*"[^"`\s]*"?\])/.source, // font-["some_font",sans-serif] /([^<>"'`\s]*\[\w*\('[^"'`\s]*'\)\])/.source, // bg-[url('...')] /([^<>"'`\s]*\[\w*\("[^"'`\s]*"\)\])/.source, // bg-[url("...")] /([^<>"'`\s]*\['[^"'`\s]*'\])/.source, // `content-['hello']` but not `content-['hello']']` diff --git a/src/util/dataTypes.js b/src/util/dataTypes.js new file mode 100644 index 000000000000..e1b616a14867 --- /dev/null +++ b/src/util/dataTypes.js @@ -0,0 +1,216 @@ +import { parseColor } from './color' + +// Ref: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Types + +let COMMA = /,(?![^(]*\))/g // Comma separator that is not located between brackets. E.g.: `cubiz-bezier(a, b, c)` these don't count. +let UNDERSCORE = /_(?![^(]*\))/g // Underscore separator that is not located between brackets. E.g.: `rgba(255,_255,_255)_black` these don't count. + +// This is not a data type, but rather a function that can normalize the +// correct values. +export function normalize(value) { + // Convert `_` to ` `, except for escaped underscores `\_` + value = value + .replace( + /([^\\])_+/g, + (fullMatch, characterBefore) => characterBefore + ' '.repeat(fullMatch.length - 1) + ) + .replace(/^_/g, ' ') + .replace(/\\_/g, '_') + + // Remove leftover whitespace + value = value.trim() + + // 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, + '$1 $2 ' + ) +} + +export function url(value) { + return value.startsWith('url(') +} + +export function number(value) { + return !isNaN(Number(value)) +} + +export function percentage(value) { + return /%$/g.test(value) || /^calc\(.+?%\)/g.test(value) +} + +let lengthUnits = [ + 'cm', + 'mm', + 'Q', + 'in', + 'pc', + 'pt', + 'px', + 'em', + 'ex', + 'ch', + 'rem', + 'lh', + 'vw', + 'vh', + 'vmin', + 'vmax', +] +let lengthUnitsPattern = `(?:${lengthUnits.join('|')})` +export function length(value) { + return ( + new RegExp(`${lengthUnitsPattern}$`).test(value) || + new RegExp(`^calc\\(.+?${lengthUnitsPattern}`).test(value) + ) +} + +let lineWidths = new Set(['thin', 'medium', 'thick']) +export function lineWidth(value) { + return lineWidths.has(value) +} + +export function color(value) { + let colors = 0 + + let result = value.split(UNDERSCORE).every((part) => { + part = normalize(part) + + if (part.startsWith('var(')) return true + if (parseColor(part) !== null) return colors++, true + + return false + }) + + if (!result) return false + return colors > 0 +} + +export function image(value) { + let images = 0 + let result = value.split(COMMA).every((part) => { + part = normalize(part) + + if (part.startsWith('var(')) return true + if ( + url(part) || + gradient(part) || + ['element(', 'image(', 'cross-fade(', 'image-set('].some((fn) => part.startsWith(fn)) + ) { + images++ + return true + } + + return false + }) + + if (!result) return false + return images > 0 +} + +let gradientTypes = new Set([ + 'linear-gradient', + 'radial-gradient', + 'repeating-linear-gradient', + 'repeating-radial-gradient', + 'conic-gradient', +]) +export function gradient(value) { + value = normalize(value) + + for (let type of gradientTypes) { + if (value.startsWith(`${type}(`)) { + return true + } + } + return false +} + +let validPositions = new Set(['center', 'top', 'right', 'bottom', 'left']) +export function position(value) { + let positions = 0 + let result = value.split(UNDERSCORE).every((part) => { + part = normalize(part) + + if (part.startsWith('var(')) return true + if (validPositions.has(part) || length(part) || percentage(part)) { + positions++ + return true + } + + return false + }) + + if (!result) return false + return positions > 0 +} + +export function familyName(value) { + let fonts = 0 + let result = value.split(COMMA).every((part) => { + part = normalize(part) + + if (part.startsWith('var(')) return true + + // If it contains spaces, then it should be quoted + if (part.includes(' ')) { + if (!/(['"])([^"']+)\1/g.test(part)) { + return false + } + } + + // If it starts with a number, it's invalid + if (/^\d/g.test(part)) { + return false + } + + fonts++ + + return true + }) + + if (!result) return false + return fonts > 0 +} + +let genericNames = new Set([ + 'serif', + 'sans-serif', + 'monospace', + 'cursive', + 'fantasy', + 'system-ui', + 'ui-serif', + 'ui-sans-serif', + 'ui-monospace', + 'ui-rounded', + 'math', + 'emoji', + 'fangsong', +]) +export function genericName(value) { + return genericNames.has(value) +} + +let absoluteSizes = new Set([ + 'xx-small', + 'x-small', + 'small', + 'medium', + 'large', + 'x-large', + 'x-large', + 'xxx-large', +]) +export function absoluteSize(value) { + return absoluteSizes.has(value) +} + +let relativeSizes = new Set(['larger', 'smaller']) +export function relativeSize(value) { + return relativeSizes.has(value) +} diff --git a/src/util/pluginUtils.js b/src/util/pluginUtils.js index 6dc02bb5cde2..73c4170a9b36 100644 --- a/src/util/pluginUtils.js +++ b/src/util/pluginUtils.js @@ -2,7 +2,21 @@ import selectorParser from 'postcss-selector-parser' import escapeCommas from './escapeCommas' import { withAlphaValue } from './withAlphaVariable' import isKeyframeRule from './isKeyframeRule' -import { parseColor } from './color' +import { + normalize, + length, + number, + percentage, + url, + color as validateColor, + genericName, + familyName, + image, + absoluteSize, + relativeSize, + position, + lineWidth, +} from './dataTypes' export function applyPseudoToMarker(selector, marker, state, join) { let states = [state] @@ -159,7 +173,7 @@ export function asValue(modifier, lookup = {}, { validate = () => true } = {}) { return value } - if (modifier[0] !== '[' || modifier[modifier.length - 1] !== ']') { + if (!isArbitraryValue(modifier)) { return undefined } @@ -169,32 +183,7 @@ export function asValue(modifier, lookup = {}, { validate = () => true } = {}) { return undefined } - // convert `_` to ` `, escept for escaped underscores `\_` - value = value - .replace(/([^\\])_/g, '$1 ') - .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, - '$1 $2 ' - ) -} - -export function asUnit(modifier, units, lookup = {}) { - return asValue(modifier, lookup, { - validate: (value) => { - let unitsPattern = `(?:${units.join('|')})` - return ( - new RegExp(`${unitsPattern}$`).test(value) || - new RegExp(`^calc\\(.+?${unitsPattern}`).test(value) - ) - }, - }) + return normalize(value) } function isArbitraryValue(input) { @@ -230,58 +219,34 @@ export function asColor(modifier, lookup = {}, tailwindConfig = {}) { return withAlphaValue(lookup[color], tailwindConfig.theme.opacity[alpha]) } - return asValue(modifier, lookup, { - validate: (value) => parseColor(value) !== null, - }) -} - -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, - [ - 'cm', - 'mm', - 'Q', - 'in', - 'pc', - 'pt', - 'px', - 'em', - 'ex', - 'ch', - 'rem', - 'lh', - 'vw', - 'vh', - 'vmin', - 'vmax', - '%', - ], - lookup - ) + return asValue(modifier, lookup, { validate: validateColor }) } export function asLookupValue(modifier, lookup = {}) { return lookup[modifier] } +function guess(validate) { + return (modifier, lookup) => { + return asValue(modifier, lookup, { validate }) + } +} + let typeMap = { any: asValue, color: asColor, - angle: asAngle, - length: asLength, - url: asURL, + url: guess(url), + image: guess(image), + length: guess(length), + percentage: guess(percentage), + position: guess(position), lookup: asLookupValue, + 'generic-name': guess(genericName), + 'family-name': guess(familyName), + number: guess(number), + 'line-width': guess(lineWidth), + 'absolute-size': guess(absoluteSize), + 'relative-size': guess(relativeSize), } let supportedTypes = Object.keys(typeMap) From d499c59305313622255c4ba00bb7d2aae40d987f Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 24 Sep 2021 17:17:13 +0200 Subject: [PATCH 11/11] add a bunch of new tests for advanced arbitrary values --- tests/arbitrary-values.test.css | 461 ++++++++++++++++++++++++++++++- tests/arbitrary-values.test.html | 345 ++++++++++++++++++----- tests/arbitrary-values.test.js | 63 +++++ 3 files changed, 793 insertions(+), 76 deletions(-) diff --git a/tests/arbitrary-values.test.css b/tests/arbitrary-values.test.css index 6d915765da01..7bb79dbb6fa2 100644 --- a/tests/arbitrary-values.test.css +++ b/tests/arbitrary-values.test.css @@ -4,6 +4,64 @@ bottom: 11px; left: 11px; } +.inset-\[var\(--value\)\] { + top: var(--value); + right: var(--value); + bottom: var(--value); + left: var(--value); +} +.inset-x-\[11px\] { + left: 11px; + right: 11px; +} +.inset-x-\[var\(--value\)\] { + left: var(--value); + right: var(--value); +} +.inset-y-\[11px\] { + top: 11px; + bottom: 11px; +} +.inset-y-\[var\(--value\)\] { + top: var(--value); + bottom: var(--value); +} +.top-\[11px\] { + top: 11px; +} +.top-\[var\(--value\)\] { + top: var(--value); +} +.right-\[11px\] { + right: 11px; +} +.right-\[var\(--value\)\] { + right: var(--value); +} +.bottom-\[11px\] { + bottom: 11px; +} +.bottom-\[var\(--value\)\] { + bottom: var(--value); +} +.left-\[11px\] { + left: 11px; +} +.left-\[var\(--value\)\] { + left: var(--value); +} +.z-\[123\] { + z-index: 123; +} +.z-\[var\(--value\)\] { + z-index: var(--value); +} +.order-\[4\] { + order: 4; +} +.order-\[var\(--value\)\] { + order: var(--value); +} .col-\[7\] { grid-column: 7; } @@ -157,6 +215,25 @@ .flex-grow-\[var\(--grow\)\] { flex-grow: var(--grow); } +.origin-\[50px_50px\] { + transform-origin: 50px 50px; +} +.translate-x-\[12\%\] { + --tw-translate-x: 12%; + transform: var(--tw-transform); +} +.translate-x-\[var\(--value\)\] { + --tw-translate-x: var(--value); + transform: var(--tw-transform); +} +.translate-y-\[12\%\] { + --tw-translate-y: 12%; + transform: var(--tw-transform); +} +.translate-y-\[var\(--value\)\] { + --tw-translate-y: var(--value); + transform: var(--tw-transform); +} .rotate-\[23deg\] { --tw-rotate: 23deg; transform: var(--tw-transform); @@ -177,16 +254,77 @@ --tw-skew-x: 3px; transform: var(--tw-transform); } +.skew-x-\[var\(--value\)\] { + --tw-skew-x: var(--value); + transform: var(--tw-transform); +} .skew-y-\[3px\] { --tw-skew-y: 3px; transform: var(--tw-transform); } +.skew-y-\[var\(--value\)\] { + --tw-skew-y: var(--value); + transform: var(--tw-transform); +} +.scale-\[0\.7\] { + --tw-scale-x: 0.7; + --tw-scale-y: 0.7; + transform: var(--tw-transform); +} +.scale-\[var\(--value\)\] { + --tw-scale-x: var(--value); + --tw-scale-y: var(--value); + transform: var(--tw-transform); +} +.scale-x-\[0\.7\] { + --tw-scale-x: 0.7; + transform: var(--tw-transform); +} +.scale-x-\[var\(--value\)\] { + --tw-scale-x: var(--value); + transform: var(--tw-transform); +} +.scale-y-\[0\.7\] { + --tw-scale-y: 0.7; + transform: var(--tw-transform); +} +.scale-y-\[var\(--value\)\] { + --tw-scale-y: var(--value); + transform: var(--tw-transform); +} +.animate-\[pong_1s_cubic-bezier\(0\2c 0\2c 0\.2\2c 1\)_infinite\] { + animation: pong 1s cubic-bezier(0, 0, 0.2, 1) infinite; +} +.animate-\[var\(--value\)\] { + animation: var(--value); +} +.cursor-\[pointer\] { + cursor: pointer; +} +.cursor-\[url\(hand\.cur\)_2_2\2c pointer\] { + cursor: url(hand.cur) 2 2, pointer; +} +.cursor-\[var\(--value\)\] { + cursor: var(--value); +} +.list-\[\'\\1f44d\'\] { + list-style-type: '\1F44D'; +} +.list-\[var\(--value\)\] { + list-style-type: var(--value); +} .columns-\[20\] { columns: 20; } .columns-\[var\(--columns\)\] { columns: var(--columns); } +.auto-cols-\[minmax\(10px\2c auto\)\] { + grid-auto-columns: minmax(10px, auto); +} +.auto-rows-\[minmax\(10px\2c auto\)\] { + grid-auto-rows: minmax(10px, auto); +} .grid-cols-\[200px\2c repeat\(auto-fill\2c minmax\(15\%\2c 100px\)\)\2c 300px\] { grid-template-columns: 200px repeat(auto-fill, minmax(15%, 100px)) 300px; } @@ -196,6 +334,24 @@ .grid-rows-\[200px\2c repeat\(auto-fill\2c minmax\(15\%\2c 100px\)\)\2c 300px\] { grid-template-rows: 200px repeat(auto-fill, minmax(15%, 100px)) 300px; } +.gap-\[20px\] { + gap: 20px; +} +.gap-\[var\(--value\)\] { + gap: var(--value); +} +.gap-x-\[20px\] { + column-gap: 20px; +} +.gap-x-\[var\(--value\)\] { + column-gap: var(--value); +} +.gap-y-\[20px\] { + row-gap: 20px; +} +.gap-y-\[var\(--value\)\] { + row-gap: var(--value); +} .space-x-\[20cm\] > :not([hidden]) ~ :not([hidden]) { --tw-space-x-reverse: 0; margin-right: calc(20cm * var(--tw-space-x-reverse)); @@ -206,6 +362,46 @@ margin-right: calc(calc(20% - 1cm) * var(--tw-space-x-reverse)); margin-left: calc(calc(20% - 1cm) * calc(1 - var(--tw-space-x-reverse))); } +.space-y-\[20cm\] > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(20cm * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(20cm * var(--tw-space-y-reverse)); +} +.space-y-\[calc\(20\%-1cm\)\] > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(calc(20% - 1cm) * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(calc(20% - 1cm) * var(--tw-space-y-reverse)); +} +.divide-x-\[20cm\] > :not([hidden]) ~ :not([hidden]) { + --tw-divide-x-reverse: 0; + border-right-width: calc(20cm * var(--tw-divide-x-reverse)); + border-left-width: calc(20cm * calc(1 - var(--tw-divide-x-reverse))); +} +.divide-x-\[calc\(20\%-1cm\)\] > :not([hidden]) ~ :not([hidden]) { + --tw-divide-x-reverse: 0; + border-right-width: calc(calc(20% - 1cm) * var(--tw-divide-x-reverse)); + border-left-width: calc(calc(20% - 1cm) * calc(1 - var(--tw-divide-x-reverse))); +} +.divide-y-\[20cm\] > :not([hidden]) ~ :not([hidden]) { + --tw-divide-y-reverse: 0; + border-top-width: calc(20cm * calc(1 - var(--tw-divide-y-reverse))); + border-bottom-width: calc(20cm * var(--tw-divide-y-reverse)); +} +.divide-y-\[calc\(20\%-1cm\)\] > :not([hidden]) ~ :not([hidden]) { + --tw-divide-y-reverse: 0; + border-top-width: calc(calc(20% - 1cm) * calc(1 - var(--tw-divide-y-reverse))); + border-bottom-width: calc(calc(20% - 1cm) * var(--tw-divide-y-reverse)); +} +.divide-\[black\] > :not([hidden]) ~ :not([hidden]) { + --tw-divide-opacity: 1; + border-color: rgb(0 0 0 / var(--tw-divide-opacity)); +} +.divide-opacity-\[0\.8\] > :not([hidden]) ~ :not([hidden]) { + --tw-divide-opacity: 0.8; +} +.divide-opacity-\[var\(--value\)\] > :not([hidden]) ~ :not([hidden]) { + --tw-divide-opacity: var(--value); +} .rounded-\[11px\] { border-radius: 11px; } @@ -240,10 +436,77 @@ .border-\[2\.5px\] { border-width: 2.5px; } +.border-\[length\:var\(--value\)\] { + border-width: var(--value); +} +.border-t-\[2\.5px\] { + border-top-width: 2.5px; +} +.border-t-\[length\:var\(--value\)\] { + border-top-width: var(--value); +} +.border-r-\[2\.5px\] { + border-right-width: 2.5px; +} +.border-r-\[length\:var\(--value\)\] { + border-right-width: var(--value); +} +.border-b-\[2\.5px\] { + border-bottom-width: 2.5px; +} +.border-b-\[length\:var\(--value\)\] { + border-bottom-width: var(--value); +} +.border-l-\[2\.5px\] { + border-left-width: 2.5px; +} +.border-l-\[length\:var\(--value\)\] { + border-left-width: var(--value); +} .border-\[\#f00\] { --tw-border-opacity: 1; border-color: rgb(255 0 0 / var(--tw-border-opacity)); } +.border-\[red_black\] { + border-color: red black; +} +.border-\[color\:var\(--value\)\] { + border-color: var(--value); +} +.border-t-\[\#f00\] { + --tw-border-opacity: 1; + border-top-color: rgb(255 0 0 / var(--tw-border-opacity)); +} +.border-t-\[color\:var\(--value\)\] { + border-top-color: var(--value); +} +.border-r-\[\#f00\] { + --tw-border-opacity: 1; + border-right-color: rgb(255 0 0 / var(--tw-border-opacity)); +} +.border-r-\[color\:var\(--value\)\] { + border-right-color: var(--value); +} +.border-b-\[\#f00\] { + --tw-border-opacity: 1; + border-bottom-color: rgb(255 0 0 / var(--tw-border-opacity)); +} +.border-b-\[color\:var\(--value\)\] { + border-bottom-color: var(--value); +} +.border-l-\[\#f00\] { + --tw-border-opacity: 1; + border-left-color: rgb(255 0 0 / var(--tw-border-opacity)); +} +.border-l-\[color\:var\(--value\)\] { + border-left-color: var(--value); +} +.border-opacity-\[0\.8\] { + --tw-border-opacity: 0.8; +} +.border-opacity-\[var\(--value\)\] { + --tw-border-opacity: var(--value); +} .bg-\[\#0f0\] { --tw-bg-opacity: 1; background-color: rgb(0 255 0 / var(--tw-bg-opacity)); @@ -262,6 +525,13 @@ .bg-\[rgba\(123\2c 123\2c 123\2c 0\.5\)\] { background-color: rgba(123, 123, 123, 0.5); } +.bg-\[rgb\(123\2c _456\2c _123\)_black\] { + background-color: rgb(123, 456, 123) black; +} +.bg-\[rgb\(123_456_789\)\] { + --tw-bg-opacity: 1; + background-color: rgb(123 456 789 / var(--tw-bg-opacity)); +} .bg-\[hsl\(0\2c 100\%\2c 50\%\)\] { --tw-bg-opacity: 1; background-color: hsl(0 100% 50% / var(--tw-bg-opacity)); @@ -269,6 +539,12 @@ .bg-\[hsla\(0\2c 100\%\2c 50\%\2c 0\.3\)\] { background-color: hsla(0, 100%, 50%, 0.3); } +.bg-\[\#0f0_var\(--value\)\] { + background-color: #0f0 var(--value); +} +.bg-\[color\:var\(--value1\)_var\(--value2\)\] { + background-color: var(--value1) var(--value2); +} .bg-opacity-\[0\.11\] { --tw-bg-opacity: 0.11; } @@ -281,6 +557,24 @@ .bg-\[url\:var\(--url\)\] { background-image: var(--url); } +.bg-\[linear-gradient\(\#eee\2c \#fff\)\] { + background-image: linear-gradient(#eee, #fff); +} +.bg-\[linear-gradient\(\#eee\2c + \#fff\)\2c + conic-gradient\(red\2c + orange\2c + yellow\2c + green\2c + blue\)\] { + background-image: linear-gradient(#eee, #fff), conic-gradient(red, orange, yellow, green, blue); +} +.bg-\[image\(\)\2c var\(--value\)\] { + background-image: image(), var(--value); +} +.bg-\[image\:var\(--value\)\2c var\(--value\)\] { + background-image: var(--value), var(--value); +} .from-\[\#da5b66\] { --tw-gradient-from: #da5b66; --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to, rgb(218 91 102 / 0)); @@ -302,17 +596,41 @@ .to-\[var\(--color\)\] { --tw-gradient-to: var(--color); } +.bg-\[length\:200px_100px\] { + background-size: 200px 100px; +} +.bg-\[length\:var\(--value\)\] { + background-size: var(--value); +} +.bg-\[position\:200px_100px\] { + background-position: 200px 100px; +} +.bg-\[position\:var\(--value\)\] { + background-position: var(--value); +} .fill-\[\#da5b66\] { fill: #da5b66; } -.fill-\[var\(--color\)\] { - fill: var(--color); +.fill-\[var\(--value\)\] { + fill: var(--value); +} +.fill-\[url\(\#icon-gradient\)\] { + fill: url(#icon-gradient); } .stroke-\[\#da5b66\] { stroke: #da5b66; } +.stroke-\[color\:var\(--value\)\] { + stroke: var(--value); +} .stroke-\[url\(\#icon-gradient\)\] { - stroke-width: url(#icon-gradient); + stroke: url(#icon-gradient); +} +.stroke-\[20px\] { + stroke-width: 20px; +} +.stroke-\[length\:var\(--value\)\] { + stroke-width: var(--value); } .object-\[50\%\2c 50\%\] { object-position: 50% 50%; @@ -323,8 +641,31 @@ .object-\[var\(--position\)\] { object-position: var(--position); } -.p-\[var\(--app-padding\)\] { - padding: var(--app-padding); +.p-\[7px\] { + padding: 7px; +} +.px-\[7px\] { + padding-left: 7px; + padding-right: 7px; +} +.py-\[7px\] { + padding-top: 7px; + padding-bottom: 7px; +} +.pt-\[7px\] { + padding-top: 7px; +} +.pr-\[7px\] { + padding-right: 7px; +} +.pb-\[7px\] { + padding-bottom: 7px; +} +.pl-\[7px\] { + padding-left: 7px; +} +.pt-\[clamp\(30px\2c 100px\)\] { + padding-top: clamp(30px, 100px); } .indent-\[50\%\] { text-indent: 50%; @@ -335,40 +676,130 @@ .align-\[10em\] { vertical-align: 10em; } +.font-\[Georgia\2c serif\] { + font-family: Georgia, serif; +} +.font-\[\'Gill_Sans\'\] { + font-family: 'Gill Sans'; +} +.font-\[sans-serif\2c serif\] { + font-family: sans-serif, serif; +} +.font-\[family-name\:var\(--value\)\] { + font-family: var(--value); +} +.font-\[serif\2c var\(--value\)\] { + font-family: serif, var(--value); +} +.font-\[\'Some_Font\'\2c sans-serif\] { + font-family: 'Some Font', sans-serif; +} +.font-\[\'Some_Font\'\2c \'Some_Other_Font\'\] { + font-family: 'Some Font', 'Some Other Font'; +} +.font-\[\'Some_Font\'\2c var\(--other-font\)\] { + font-family: 'Some Font', var(--other-font); +} .text-\[2\.23rem\] { font-size: 2.23rem; } .text-\[length\:var\(--font-size\)\] { font-size: var(--font-size); } +.font-\[300\] { + font-weight: 300; +} +.font-\[number\:lighter\] { + font-weight: lighter; +} +.font-\[number\:var\(--value\)\] { + font-weight: var(--value); +} .leading-\[var\(--leading\)\] { line-height: var(--leading); } .tracking-\[var\(--tracking\)\] { letter-spacing: var(--tracking); } +.text-\[black\] { + --tw-text-opacity: 1; + color: rgb(0 0 0 / var(--tw-text-opacity)); +} +.text-\[rgb\(123\2c 123\2c 123\)\] { + --tw-text-opacity: 1; + color: rgb(123 123 123 / var(--tw-text-opacity)); +} +.text-\[rgb\(123\2c _123\2c _123\)\] { + --tw-text-opacity: 1; + color: rgb(123 123 123 / var(--tw-text-opacity)); +} +.text-\[rgb\(123_123_123\)\] { + --tw-text-opacity: 1; + color: rgb(123 123 123 / var(--tw-text-opacity)); +} .text-\[color\:var\(--color\)\] { color: var(--color); } +.text-opacity-\[0\.8\] { + --tw-text-opacity: 0.8; +} +.text-opacity-\[var\(--value\)\] { + --tw-text-opacity: var(--value); +} .placeholder-\[var\(--placeholder\)\]::placeholder { color: var(--placeholder); } .placeholder-opacity-\[var\(--placeholder-opacity\)\]::placeholder { --tw-placeholder-opacity: var(--placeholder-opacity); } +.caret-\[black\] { + caret-color: black; +} +.caret-\[var\(--value\)\] { + caret-color: var(--value); +} .accent-\[\#bada55\] { accent-color: #bada55; } .accent-\[var\(--accent-color\)\] { accent-color: var(--accent-color); } +.opacity-\[0\.8\] { + opacity: 0.8; +} .opacity-\[var\(--opacity\)\] { opacity: var(--opacity); } +.shadow-\[0px_1px_2px_black\] { + --tw-shadow: 0px 1px 2px black; + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), + var(--tw-shadow); +} +.shadow-\[var\(--value\)\] { + --tw-shadow: var(--value); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), + var(--tw-shadow); +} +.outline-\[2px_solid_black\] { + outline: 2px solid black; + outline-offset: 0; +} +.outline-\[2px_solid_black\2c 2px\] { + outline: 2px solid black; + outline-offset: 2px; +} .outline-\[var\(--outline\)\] { outline: var(--outline); outline-offset: 0; } +.outline-\[var\(--outline\)\2c 3px\] { + outline: var(--outline); + outline-offset: 3px; +} +.outline-\[2px_solid_black\2c var\(--outline-offset\)\] { + outline: 2px solid black; + outline-offset: var(--outline-offset); +} .ring-\[10px\] { --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); @@ -376,22 +807,38 @@ var(--tw-ring-color); box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); } +.ring-\[length\:\(var\(--value\)\)\] { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) + var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc((var(--value)) + var(--tw-ring-offset-width)) + var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); +} .ring-\[\#76ad65\] { --tw-ring-opacity: 1; --tw-ring-color: rgb(118 173 101 / var(--tw-ring-opacity)); } +.ring-\[color\:var\(--value\)\] { + --tw-ring-color: var(--value); +} .ring-opacity-\[var\(--ring-opacity\)\] { --tw-ring-opacity: var(--ring-opacity); } .ring-offset-\[19rem\] { --tw-ring-offset-width: 19rem; } +.ring-offset-\[length\:var\(--value\)\] { + --tw-ring-offset-width: var(--value); +} .ring-offset-\[\#76ad65\] { --tw-ring-offset-color: #76ad65; } .ring-offset-\[\#ad672f\] { --tw-ring-offset-color: #ad672f; } +.ring-offset-\[color\:var\(--value\)\] { + --tw-ring-offset-color: var(--value); +} .blur-\[15px\] { --tw-blur: blur(15px); filter: var(--tw-filter); @@ -404,6 +851,10 @@ --tw-contrast: contrast(2.4); filter: var(--tw-filter); } +.drop-shadow-\[0px_1px_2px_black\] { + --tw-drop-shadow: drop-shadow(0px 1px 2px black); + filter: var(--tw-filter); +} .grayscale-\[0\.55\] { --tw-grayscale: grayscale(0.55); filter: var(--tw-filter); diff --git a/tests/arbitrary-values.test.html b/tests/arbitrary-values.test.html index bab407ed8649..651aa68c856c 100644 --- a/tests/arbitrary-values.test.html +++ b/tests/arbitrary-values.test.html @@ -8,70 +8,142 @@ -
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
+
+ +
+ +
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ +
+
+
+
+
+
+ +
+
+ +
+
+
+ +
+
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+ +
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ +
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+ +
+
+ +
+
+
-
+
+
+ +
+
+
+ +
+
+
-
-
+ +
+
+
+
+
+
+
+
+ +
+ +
+ +
+
+
+
+
+
+
+
+
+
+ +
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+ +
+
+ +
+
+ +
+ +
+
+ +
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
-
-
diff --git a/tests/arbitrary-values.test.js b/tests/arbitrary-values.test.js index 17252be92ef9..f264faa0c9e4 100644 --- a/tests/arbitrary-values.test.js +++ b/tests/arbitrary-values.test.js @@ -16,6 +16,58 @@ test('arbitrary values', () => { }) }) +it('should support arbitrary values for various background utilities', () => { + let config = { + content: [ + { + raw: html` + +
+
+ + +
+
+ + +
+
+ `, + }, + ], + } + + return run('@tailwind utilities', config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + .bg-red-500 { + --tw-bg-opacity: 1; + background-color: rgb(239 68 68 / var(--tw-bg-opacity)); + } + + .bg-\\[\\#ff0000\\] { + --tw-bg-opacity: 1; + background-color: rgb(255 0 0 / var(--tw-bg-opacity)); + } + + .bg-\\[color\\:var\\(--bg-color\\)\\] { + background-color: var(--bg-color); + } + + .bg-gradient-to-r { + background-image: linear-gradient(to right, var(--tw-gradient-stops)); + } + + .bg-\\[url\\(\\'\\/image-1-0\\.png\\'\\)\\] { + background-image: url('/image-1-0.png'); + } + + .bg-\\[url\\:var\\(--image-url\\)\\] { + background-image: var(--image-url); + } + `) + }) +}) + it('should not generate any css if an unknown typehint is used', () => { let config = { content: [ @@ -59,6 +111,8 @@ it('should convert _ to spaces', () => {
+
+
`, }, ], @@ -113,9 +167,18 @@ it('should convert _ to spaces', () => { --tw-drop-shadow: drop-shadow(0px 1px 3px black); filter: var(--tw-filter); } + .content-\\[_hello_world_\\] { content: hello world; } + + .content-\\[___abc____\\] { + content: abc; + } + + .content-\\[\\'__hello__world__\\'\\] { + content: ' hello world '; + } `) }) })