diff --git a/CHANGELOG.md b/CHANGELOG.md index b88542fdae72..5cca63f00e97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Ensure there's always a space before `!important` when stringifying CSS ([#14611](https://github.com/tailwindlabs/tailwindcss/pull/14611)) - Don't set `display: none` on elements that use `hidden="until-found"` ([#14631](https://github.com/tailwindlabs/tailwindcss/pull/14631)) - Fix issue that could cause the CLI to crash when files are deleted while watching ([#14616](https://github.com/tailwindlabs/tailwindcss/pull/14616)) +- Ensure custom variants using the JS API have access to modifiers ([#14637](https://github.com/tailwindlabs/tailwindcss/pull/14637)) - _Upgrade (experimental)_: Ensure CSS before a layer stays unlayered when running codemods ([#14596](https://github.com/tailwindlabs/tailwindcss/pull/14596)) - _Upgrade (experimental)_: Resolve issues where some prefixed candidates were not properly migrated ([#14600](https://github.com/tailwindlabs/tailwindcss/pull/14600)) diff --git a/packages/tailwindcss/src/compat/plugin-api.test.ts b/packages/tailwindcss/src/compat/plugin-api.test.ts index c0e04d576b09..08c62a4ccaa4 100644 --- a/packages/tailwindcss/src/compat/plugin-api.test.ts +++ b/packages/tailwindcss/src/compat/plugin-api.test.ts @@ -2295,6 +2295,52 @@ describe('matchVariant', () => { }" `) }) + + test('should be called with eventual modifiers', async () => { + let { build } = await compile( + css` + @plugin "my-plugin"; + @tailwind utilities; + `, + { + loadModule: async (id, base) => { + return { + base, + module: ({ matchVariant }: PluginAPI) => { + matchVariant('my-container', (value, { modifier }) => { + function parseValue(value: string) { + const numericValue = value.match(/^(\d+\.\d+|\d+|\.\d+)\D+/)?.[1] ?? null + if (numericValue === null) return null + + return parseFloat(value) + } + + const parsed = parseValue(value) + return parsed !== null ? `@container ${modifier ?? ''} (min-width: ${value})` : [] + }) + }, + } + }, + }, + ) + let compiled = build([ + 'my-container-[250px]:underline', + 'my-container-[250px]/placement:underline', + ]) + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@container (width >= 250px) { + .my-container-\\[250px\\]\\:underline { + text-decoration-line: underline; + } + } + + @container placement (width >= 250px) { + .my-container-\\[250px\\]\\/placement\\:underline { + text-decoration-line: underline; + } + }" + `) + }) }) describe('addUtilities()', () => { diff --git a/packages/tailwindcss/src/compat/plugin-api.ts b/packages/tailwindcss/src/compat/plugin-api.ts index c4d28de9a9e3..77069aa194b3 100644 --- a/packages/tailwindcss/src/compat/plugin-api.ts +++ b/packages/tailwindcss/src/compat/plugin-api.ts @@ -116,9 +116,13 @@ export function buildPluginApi( designSystem.variants.group( () => { designSystem.variants.functional(name, (ruleNodes, variant) => { - if (!variant.value || variant.modifier) { + if (!variant.value) { if (options?.values && 'DEFAULT' in options.values) { - ruleNodes.nodes = resolveVariantValue(options.values.DEFAULT, null, ruleNodes.nodes) + ruleNodes.nodes = resolveVariantValue( + options.values.DEFAULT, + variant.modifier, + ruleNodes.nodes, + ) return } return null @@ -136,7 +140,7 @@ export function buildPluginApi( return } - ruleNodes.nodes = resolveVariantValue(defaultValue, null, ruleNodes.nodes) + ruleNodes.nodes = resolveVariantValue(defaultValue, variant.modifier, ruleNodes.nodes) } }) },