diff --git a/src/lib/generateRules.js b/src/lib/generateRules.js index 8b1c2843f628..3695876140b0 100644 --- a/src/lib/generateRules.js +++ b/src/lib/generateRules.js @@ -145,7 +145,7 @@ function applyVariant(variant, matches, context) { } /** @type {{modifier: string | null, value: string | null}} */ - let args = { modifier: null, value: null } + let args = { modifier: null, value: sharedState.NONE } // Retrieve "modifier" { diff --git a/src/lib/setupContextUtils.js b/src/lib/setupContextUtils.js index bd4071bd377d..4e6cc96e9038 100644 --- a/src/lib/setupContextUtils.js +++ b/src/lib/setupContextUtils.js @@ -577,6 +577,8 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs let modifiersEnabled = flagEnabled(tailwindConfig, 'generalizedModifiers') for (let [key, value] of Object.entries(options?.values ?? {})) { + if (key === 'DEFAULT') continue + api.addVariant( isSpecial ? `${variant}${key}` : `${variant}-${key}`, ({ args, container }) => @@ -594,13 +596,20 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs ) } + let hasDefault = 'DEFAULT' in (options?.values ?? {}) + api.addVariant( variant, - ({ args, container }) => - variantFn( - args.value, + ({ args, container }) => { + if (args.value === sharedState.NONE && !hasDefault) { + return null + } + + return variantFn( + args.value === sharedState.NONE ? options.values.DEFAULT : args.value, modifiersEnabled ? { modifier: args.modifier, container } : { container } - ), + ) + }, { ...options, id, diff --git a/src/lib/sharedState.js b/src/lib/sharedState.js index aeaf45226905..caf7a7319d06 100644 --- a/src/lib/sharedState.js +++ b/src/lib/sharedState.js @@ -8,6 +8,8 @@ export const contextSourcesMap = new Map() export const sourceHashMap = new Map() export const NOT_ON_DEMAND = new String('*') +export const NONE = Symbol('__NONE__') + export function resolveDebug(debug) { if (debug === undefined) { return false diff --git a/tests/match-variants.test.js b/tests/match-variants.test.js index 7da1b42325df..dd839151fe55 100644 --- a/tests/match-variants.test.js +++ b/tests/match-variants.test.js @@ -656,3 +656,135 @@ it('should guarantee that we are not passing values from other variants to the w `) }) }) + +it('should default to the DEFAULT value for variants', () => { + let config = { + content: [ + { + raw: html` +
+
+
+ `, + }, + ], + corePlugins: { preflight: false }, + plugins: [ + ({ matchVariant }) => { + matchVariant('foo', (value) => `.foo${value} &`, { + values: { + DEFAULT: '.bar', + }, + }) + }, + ], + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .foo.bar .foo\:underline { + text-decoration-line: underline; + } + `) + }) +}) + +it('should not generate anything if the matchVariant does not have a DEFAULT value configured', () => { + let config = { + content: [ + { + raw: html` +
+
+
+ `, + }, + ], + corePlugins: { preflight: false }, + plugins: [ + ({ matchVariant }) => { + matchVariant('foo', (value) => `.foo${value} &`) + }, + ], + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css``) + }) +}) + +it('should be possible to use `null` as a DEFAULT value', () => { + let config = { + content: [ + { + raw: html` +
+
+
+ `, + }, + ], + corePlugins: { preflight: false }, + plugins: [ + ({ matchVariant }) => { + matchVariant('foo', (value) => `.foo${value === null ? '-good' : '-bad'} &`, { + values: { DEFAULT: null }, + }) + }, + ], + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .foo-good .foo\:underline { + text-decoration-line: underline; + } + `) + }) +}) + +it('should be possible to use `undefined` as a DEFAULT value', () => { + let config = { + content: [ + { + raw: html` +
+
+
+ `, + }, + ], + corePlugins: { preflight: false }, + plugins: [ + ({ matchVariant }) => { + matchVariant('foo', (value) => `.foo${value === undefined ? '-good' : '-bad'} &`, { + values: { DEFAULT: undefined }, + }) + }, + ], + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .foo-good .foo\:underline { + text-decoration-line: underline; + } + `) + }) +})