diff --git a/CHANGELOG.md b/CHANGELOG.md index 75ce2d203..f044c8035 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add `fill-none` and `stroke-none` utilities by default ([#9403](https://github.com/tailwindlabs/tailwindcss/pull/9403)) - Support `sort` function in `matchVariant` ([#9423](https://github.com/tailwindlabs/tailwindcss/pull/9423)) - Implement the `supports` variant ([#9453](https://github.com/tailwindlabs/tailwindcss/pull/9453)) +- Add experimental `label`s for variants ([#9456](https://github.com/tailwindlabs/tailwindcss/pull/9456)) ### Fixed diff --git a/src/corePlugins.js b/src/corePlugins.js index 352737c70..e6caa7ca3 100644 --- a/src/corePlugins.js +++ b/src/corePlugins.js @@ -75,7 +75,7 @@ export let variantPlugins = { }) }, - pseudoClassVariants: ({ addVariant, config }) => { + pseudoClassVariants: ({ addVariant, matchVariant, config }) => { let pseudoVariants = [ // Positional ['first', '&:first-child'], @@ -143,20 +143,33 @@ export let variantPlugins = { }) } - for (let [variantName, state] of pseudoVariants) { - addVariant(`group-${variantName}`, (ctx) => { - let result = typeof state === 'function' ? state(ctx) : state - - return result.replace(/&(\S+)/, ':merge(.group)$1 &') - }) + let variants = { + group: ({ label }) => + label ? [`:merge(.group\\<${label}\\>)`, ' &'] : [`:merge(.group)`, ' &'], + peer: ({ label }) => + label ? [`:merge(.peer\\<${label}\\>)`, ' ~ &'] : [`:merge(.peer)`, ' ~ &'], } - for (let [variantName, state] of pseudoVariants) { - addVariant(`peer-${variantName}`, (ctx) => { - let result = typeof state === 'function' ? state(ctx) : state + for (let [name, fn] of Object.entries(variants)) { + matchVariant( + name, + (ctx = {}) => { + let { label, value = '' } = ctx + if (label) { + log.warn(`labelled-${name}-experimental`, [ + `The labelled ${name} feature in Tailwind CSS is currently in preview.`, + 'Preview features are not covered by semver, and may be improved in breaking ways at any time.', + ]) + } - return result.replace(/&(\S+)/, ':merge(.peer)$1 ~ &') - }) + let result = normalize(typeof value === 'function' ? value(ctx) : value) + if (!result.includes('&')) result = '&' + result + + let [a, b] = fn({ label }) + return result.replace(/&(\S+)?/g, (_, pseudo = '') => a + pseudo + b) + }, + { values: Object.fromEntries(pseudoVariants) } + ) } }, @@ -216,9 +229,7 @@ export let variantPlugins = { } }, - supportsVariants: ({ matchVariant, theme, config }) => { - if (!flagEnabled(config(), 'matchVariant')) return - + supportsVariants: ({ matchVariant, theme }) => { matchVariant( 'supports', ({ value = '' }) => { diff --git a/src/featureFlags.js b/src/featureFlags.js index a03916c5b..2bfee94d9 100644 --- a/src/featureFlags.js +++ b/src/featureFlags.js @@ -14,7 +14,6 @@ let featureFlags = { ], experimental: [ 'optimizeUniversalDefaults', - 'matchVariant', // 'variantGrouping', ], } diff --git a/src/lib/generateRules.js b/src/lib/generateRules.js index a78557f20..d0955de61 100644 --- a/src/lib/generateRules.js +++ b/src/lib/generateRules.js @@ -128,12 +128,24 @@ function applyVariant(variant, matches, context) { return matches } - let args + let args = {} - // Find partial arbitrary variants + // Retrieve "label" + { + let match = /^[\w-]+\<(.*)\>-/g.exec(variant) + if (match) { + variant = variant.replace(`<${match[1]}>`, '') + args.label = match[1] + } + } + + // Retrieve "arbitrary value" if (variant.endsWith(']') && !variant.startsWith('[')) { - args = variant.slice(variant.lastIndexOf('[') + 1, -1) - variant = variant.slice(0, variant.indexOf(args) - 1 /* - */ - 1 /* [ */) + let match = /-?\[(.*)\]/g.exec(variant) + if (match) { + variant = variant.replace(match[0], '') + args.value = match[1] + } } // Register arbitrary variants @@ -297,7 +309,7 @@ function applyVariant(variant, matches, context) { sort: context.offsets.applyVariantOffset( meta.sort, variantSort, - Object.assign({ value: args }, context.variantOptions.get(variant)) + Object.assign(args, context.variantOptions.get(variant)) ), collectedFormats: (meta.collectedFormats ?? []).concat(collectedFormats), isArbitraryVariant: isArbitraryValue(variant), diff --git a/src/lib/setupContextUtils.js b/src/lib/setupContextUtils.js index 874b7f705..fb51e06eb 100644 --- a/src/lib/setupContextUtils.js +++ b/src/lib/setupContextUtils.js @@ -4,7 +4,6 @@ import postcss from 'postcss' import dlv from 'dlv' import selectorParser from 'postcss-selector-parser' -import { flagEnabled } from '../featureFlags.js' import transformThemeValue from '../util/transformThemeValue' import parseObjectStyles from '../util/parseObjectStyles' import prefixSelector from '../util/prefixSelector' @@ -256,6 +255,7 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs } ) + let variantIdentifier = 0 let api = { postcss, prefix: applyConfiguredPrefix, @@ -518,23 +518,27 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs variantMap.set(variantName, variantFunctions) context.variantOptions.set(variantName, options) }, - } - - if (flagEnabled(tailwindConfig, 'matchVariant')) { - let variantIdentifier = 0 - api.matchVariant = function (variant, variantFn, options) { + matchVariant(variant, variantFn, options) { let id = ++variantIdentifier // A unique identifier that "groups" these variables together. for (let [key, value] of Object.entries(options?.values ?? {})) { - api.addVariant(`${variant}-${key}`, variantFn({ value }), { ...options, value, id }) + api.addVariant( + `${variant}-${key}`, + Object.assign(({ args, container }) => variantFn({ ...args, container, value }), { + [MATCH_VARIANT]: true, + }), + { ...options, value, id } + ) } api.addVariant( variant, - Object.assign(({ args }) => variantFn({ value: args }), { [MATCH_VARIANT]: true }), + Object.assign(({ args, container }) => variantFn({ ...args, container }), { + [MATCH_VARIANT]: true, + }), { ...options, id } ) - } + }, } return api diff --git a/tests/arbitrary-variants.test.js b/tests/arbitrary-variants.test.js index a947dbe43..a55fe008b 100644 --- a/tests/arbitrary-variants.test.js +++ b/tests/arbitrary-variants.test.js @@ -618,7 +618,6 @@ test('classes in the same arbitrary variant should not be prefixed', () => { it('should support supports', () => { let config = { - experimental: { matchVariant: true }, theme: { supports: { grid: 'display: grid', @@ -707,3 +706,205 @@ it('should support supports', () => { `) }) }) + +it('should be possible to use labels and arbitrary groups', () => { + let config = { + content: [ + { + raw: html` +