mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
Ignore custom variants with :merge(…) selectors (#18020)
Closes #15617 ## Summary This PR ignores `addVariant(…)` legacy JS plugin calls for variants that are using the [`:merge(…)` selector](https://v3.tailwindcss.com/docs/plugins#parent-and-sibling-states) for parent and sibling states. We can ignore these now because in v4, `group-*` and `peer-*` variants _compound automatically_ and you don't have to define them anymore. ## Test plan Added a unit test to ensure that the `optional` variant example from the v3 docs work as expected.
This commit is contained in:
parent
e57a2f5a3a
commit
f3157cd9a6
@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Ensure that running the Standalone build does not leave temporary files behind ([#17981](https://github.com/tailwindlabs/tailwindcss/pull/17981))
|
||||
- Fix `-rotate-*` utilities with arbitrary values ([#18014](https://github.com/tailwindlabs/tailwindcss/pull/18014))
|
||||
- Upgrade: Change casing of utilities with named values to kebab-case to match updated theme variables ([#18017](https://github.com/tailwindlabs/tailwindcss/pull/18017))
|
||||
- Ignore custom variants using `:merge(…)` selectors in legacy JS plugins ([#18020](https://github.com/tailwindlabs/tailwindcss/pull/18020))
|
||||
|
||||
### Added
|
||||
|
||||
|
||||
@ -1830,6 +1830,44 @@ describe('addVariant', () => {
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
test('ignores variants that use :merge(…) and ensures `peer-*` and `group-*` rules work out of the box', async () => {
|
||||
let { build } = await compile(
|
||||
css`
|
||||
@plugin "my-plugin";
|
||||
@layer utilities {
|
||||
@tailwind utilities;
|
||||
}
|
||||
`,
|
||||
{
|
||||
loadModule: async (id, base) => {
|
||||
return {
|
||||
path: '',
|
||||
base,
|
||||
module: ({ addVariant }: PluginAPI) => {
|
||||
addVariant('optional', '&:optional')
|
||||
addVariant('group-optional', { ':merge(.group):optional &': '@slot' })
|
||||
addVariant('peer-optional', { '&': { ':merge(.peer):optional ~ &': '@slot' } })
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
)
|
||||
let compiled = build([
|
||||
'optional:flex',
|
||||
'group-optional:flex',
|
||||
'peer-optional:flex',
|
||||
'group-optional/foo:flex',
|
||||
])
|
||||
|
||||
expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(`
|
||||
"@layer utilities {
|
||||
.group-optional\\:flex:is(:where(.group):optional *), .group-optional\\/foo\\:flex:is(:where(.group\\/foo):optional *), .peer-optional\\:flex:is(:where(.peer):optional ~ *), .optional\\:flex:optional {
|
||||
display: flex;
|
||||
}
|
||||
}"
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
describe('matchVariant', () => {
|
||||
@ -2702,6 +2740,44 @@ describe('matchVariant', () => {
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
test('ignores variants that use :merge(…)', async () => {
|
||||
let { build } = await compile(
|
||||
css`
|
||||
@plugin "my-plugin";
|
||||
@layer utilities {
|
||||
@tailwind utilities;
|
||||
}
|
||||
`,
|
||||
{
|
||||
loadModule: async (id, base) => {
|
||||
return {
|
||||
path: '',
|
||||
base,
|
||||
module: ({ matchVariant }: PluginAPI) => {
|
||||
matchVariant('optional', (flavor) => `&:optional:has(${flavor}) &`)
|
||||
matchVariant('group-optional', (flavor) => `:merge(.group):optional:has(${flavor}) &`)
|
||||
matchVariant('peer-optional', (flavor) => `:merge(.peer):optional:has(${flavor}) ~ &`)
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
)
|
||||
let compiled = build([
|
||||
'optional-[test]:flex',
|
||||
'group-optional-[test]:flex',
|
||||
'peer-optional-[test]:flex',
|
||||
'group-optional-[test]/foo:flex',
|
||||
])
|
||||
|
||||
expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(`
|
||||
"@layer utilities {
|
||||
.group-optional-\\[test\\]\\:flex:is(:where(.group):optional:has(test) :where(.group) *), .group-optional-\\[test\\]\\/foo\\:flex:is(:where(.group\\/foo):optional:has(test) :where(.group\\/foo) *), .peer-optional-\\[test\\]\\:flex:is(:where(.peer):optional:has(test) :where(.peer) ~ *), .optional-\\[test\\]\\:flex:optional:has(test) .optional-\\[test\\]\\:flex {
|
||||
display: flex;
|
||||
}
|
||||
}"
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
describe('addUtilities()', () => {
|
||||
|
||||
@ -115,6 +115,22 @@ export function buildPluginApi({
|
||||
)
|
||||
}
|
||||
|
||||
// Ignore variants emitting v3 `:merge(…)` rules. In v4, the `group-*` and `peer-*` variants
|
||||
// compound automatically.
|
||||
if (typeof variant === 'string') {
|
||||
if (variant.includes(':merge(')) return
|
||||
} else if (Array.isArray(variant)) {
|
||||
if (variant.some((v) => v.includes(':merge('))) return
|
||||
} else if (typeof variant === 'object') {
|
||||
function keyIncludes(object: Record<string, any>, search: string): boolean {
|
||||
return Object.entries(object).some(
|
||||
([key, value]) =>
|
||||
key.includes(search) || (typeof value === 'object' && keyIncludes(value, search)),
|
||||
)
|
||||
}
|
||||
if (keyIncludes(variant, ':merge(')) return
|
||||
}
|
||||
|
||||
// Single selector or multiple parallel selectors
|
||||
if (typeof variant === 'string' || Array.isArray(variant)) {
|
||||
designSystem.variants.static(
|
||||
@ -143,6 +159,17 @@ export function buildPluginApi({
|
||||
return parseVariantValue(resolved, nodes)
|
||||
}
|
||||
|
||||
try {
|
||||
// Sample variant value and ignore variants emitting v3 `:merge` rules. In
|
||||
// v4, the `group-*` and `peer-*` variants compound automatically.
|
||||
let sample = fn('a', { modifier: null })
|
||||
if (typeof sample === 'string' && sample.includes(':merge(')) {
|
||||
return
|
||||
} else if (Array.isArray(sample) && sample.some((r) => r.includes(':merge('))) {
|
||||
return
|
||||
}
|
||||
} catch {}
|
||||
|
||||
let defaultOptionKeys = Object.keys(options?.values ?? {})
|
||||
designSystem.variants.group(
|
||||
() => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user