Canonicalization: migrate [&:has(…)] to has-[…] variant (#19991)
This PR improves the canonicalization around arbitrary variants containing `[&:has(…)]`, by converting them to `has-[…]`. Essentially when you have a variant, with `&:has(…)`, we will convert it: ```diff - [&:has([role=checkbox])]:flex + has-[[role=checkbox]]:flex ``` This also means that if the arbitrary selector inside of the `&:has(…)` can be converted to something that aligns with known variants, then we will do that as well: ```diff // `data-*` can be hoisted outside of the arbitrary value - [&:has([data-slot=description])]:flex + has-data-[slot=description]:flex // `aria-visible="true"` can be entirely replaced by `aria-visible` - [&:has([aria-visible="true"])]:flex + has-aria-visible:flex ``` Noticed this while looking into: https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1562 ## Test plan 1. Added additional tests for this use case 2. Existing tests still pass
This commit is contained in:
parent
b9449612ba
commit
73f3a6a743
3 changed files with 28 additions and 0 deletions
|
|
@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Canonicalization: preserve significant `_` whitespace in arbitrary values ([#19986](https://github.com/tailwindlabs/tailwindcss/pull/19986))
|
||||
- Canonicalization: add parentheses when removing whitespace from arbitrary values would hurt readability ([#19986](https://github.com/tailwindlabs/tailwindcss/pull/19986))
|
||||
- Canonicalization: preserve the original unit in arbitrary values instead of normalizing to base units (e.g. `-mt-[20in]` → `mt-[-20in]`, not `mt-[-1920px]`) ([#19988](https://github.com/tailwindlabs/tailwindcss/pull/19988))
|
||||
- Canonicalization: migrate arbitrary `:has()` variants from `[&:has(…)]` to `has-[…]` ([#19991](https://github.com/tailwindlabs/tailwindcss/pull/19991))
|
||||
|
||||
## [4.2.4] - 2026-04-21
|
||||
|
||||
|
|
|
|||
|
|
@ -1022,6 +1022,11 @@ describe.each([['default'], ['with-variant'], ['important'], ['prefix']])('%s',
|
|||
['has-[[aria-visible]]:flex', 'has-aria-[visible]:flex'],
|
||||
|
||||
['has-[&:not(:nth-child(even))]:flex', 'has-odd:flex'],
|
||||
|
||||
// Arbitrary variant to compound + arbitrary variants
|
||||
['[&:has([role=checkbox])]:flex', 'has-[[role=checkbox]]:flex'],
|
||||
['[&:has([aria-visible="true"])]:flex', 'has-aria-visible:flex'],
|
||||
['[&:has([data-slot=description])]:flex', 'has-data-[slot=description]:flex'],
|
||||
])(testName, { timeout }, async (candidate, expected) => {
|
||||
let input = css`
|
||||
@import 'tailwindcss';
|
||||
|
|
|
|||
|
|
@ -1796,6 +1796,28 @@ function modernizeArbitraryValuesVariant(
|
|||
continue
|
||||
}
|
||||
|
||||
// `[&:has(…)]` can be replaced with `has-[…]`
|
||||
if (
|
||||
// Only top-level, so `group-[&:has(…)]` is not covered
|
||||
|
||||
parent === null &&
|
||||
// [&:has(…)]:flex
|
||||
// ^ ^^^^^^
|
||||
ast.length === 2 &&
|
||||
ast[0].kind === 'selector' &&
|
||||
ast[0].value === '&' &&
|
||||
ast[1].kind === 'function' &&
|
||||
ast[1].value === ':has' &&
|
||||
ast[1].nodes.length === 1 &&
|
||||
ast[1].nodes[0].kind === 'selector'
|
||||
) {
|
||||
replaceObject(
|
||||
variant,
|
||||
designSystem.parseVariant(`has-[${SelectorParser.toCss(ast[1].nodes)}]`),
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
// `in-*` variant. If the selector ends with ` &`, we can convert it to an
|
||||
// `in-*` variant.
|
||||
//
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue