Ensure not-* does not remove :is(…) (#16825)
Resolves #16821 This PR removes a special case in the `not-*` variant compound that removed `:is(…)` if it was the only part of the inversed selector list. While in-theory this makes sense, `:is(…)` accepts a _forgiving_ selector list where as `:not(…)` does not. See the [last point here](https://developer.mozilla.org/en-US/docs/Web/CSS/:not#description). This is an issue specifically in combinations with variants that have selectors that are not supported by all browsers yet, for example `:open`. It seems to be the most expected to simply keep the `:is(…)` here in any case. ## Test plan - Ensured the repro form #16821 now also works in browsers that do not support `:open` (Safari and Firefox at the time of writing this): <img width="484" alt="Screenshot 2025-02-26 at 15 36 22" src="https://github.com/user-attachments/assets/f3391693-895b-4e44-8566-95e2960ec4e3" />
This commit is contained in:
parent
124b82bc79
commit
e938c58778
3 changed files with 12 additions and 20 deletions
|
|
@ -14,6 +14,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- _Experimental_: Add `user-valid` and `user-invalid` variants ([#12370](https://github.com/tailwindlabs/tailwindcss/pull/12370))
|
||||
- _Experimental_: Add `wrap-anywhere`, `wrap-break-word`, and `wrap-normal` utilities ([#12128](https://github.com/tailwindlabs/tailwindcss/pull/12128))
|
||||
|
||||
### Fixed
|
||||
|
||||
- Ensure `not-*` does not remove `:is(…)` from variants ([#16825](https://github.com/tailwindlabs/tailwindcss/pull/16825))
|
||||
|
||||
## [4.0.9] - 2025-02-25
|
||||
|
||||
### Fixed
|
||||
|
|
|
|||
|
|
@ -232,11 +232,12 @@ test('target', async () => {
|
|||
})
|
||||
|
||||
test('open', async () => {
|
||||
expect(await run(['open:flex', 'group-open:flex', 'peer-open:flex'])).toMatchInlineSnapshot(`
|
||||
".group-open\\:flex:is(:where(.group):is([open], :popover-open, :open) *), .peer-open\\:flex:is(:where(.peer):is([open], :popover-open, :open) ~ *), .open\\:flex:is([open], :popover-open, :open) {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(await run(['open:flex', 'group-open:flex', 'peer-open:flex', 'not-open:flex']))
|
||||
.toMatchInlineSnapshot(`
|
||||
".not-open\\:flex:not(:is([open], :popover-open, :open)), .group-open\\:flex:is(:where(.group):is([open], :popover-open, :open) *), .peer-open\\:flex:is(:where(.peer):is([open], :popover-open, :open) ~ *), .open\\:flex:is([open], :popover-open, :open) {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(await run(['open/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
|
|
@ -1449,15 +1450,7 @@ test('not', async () => {
|
|||
],
|
||||
),
|
||||
).toMatchInlineSnapshot(`
|
||||
".not-first\\:flex:not(:first-child), .not-last\\:flex:not(:last-child), .not-only\\:flex:not(:only-child), .not-odd\\:flex:not(:nth-child(odd)), .not-even\\:flex:not(:nth-child(2n)), .not-first-of-type\\:flex:not(:first-of-type), .not-last-of-type\\:flex:not(:last-of-type), .not-only-of-type\\:flex:not(:only-of-type), .not-visited\\:flex:not(:visited), .not-target\\:flex:not(:target) {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.not-open\\:flex:not([open], :popover-open, :open) {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.not-default\\:flex:not(:default), .not-checked\\:flex:not(:checked), .not-indeterminate\\:flex:not(:indeterminate), .not-placeholder-shown\\:flex:not(:placeholder-shown), .not-autofill\\:flex:not(:autofill), .not-optional\\:flex:not(:optional), .not-required\\:flex:not(:required), .not-valid\\:flex:not(:valid), .not-invalid\\:flex:not(:invalid), .not-in-range\\:flex:not(:in-range), .not-out-of-range\\:flex:not(:out-of-range), .not-read-only\\:flex:not(:read-only), .not-empty\\:flex:not(:empty), .not-focus-within\\:flex:not(:focus-within), .not-hover\\:flex:not(:hover) {
|
||||
".not-first\\:flex:not(:first-child), .not-last\\:flex:not(:last-child), .not-only\\:flex:not(:only-child), .not-odd\\:flex:not(:nth-child(odd)), .not-even\\:flex:not(:nth-child(2n)), .not-first-of-type\\:flex:not(:first-of-type), .not-last-of-type\\:flex:not(:last-of-type), .not-only-of-type\\:flex:not(:only-of-type), .not-visited\\:flex:not(:visited), .not-target\\:flex:not(:target), .not-open\\:flex:not(:is([open], :popover-open, :open)), .not-default\\:flex:not(:default), .not-checked\\:flex:not(:checked), .not-indeterminate\\:flex:not(:indeterminate), .not-placeholder-shown\\:flex:not(:placeholder-shown), .not-autofill\\:flex:not(:autofill), .not-optional\\:flex:not(:optional), .not-required\\:flex:not(:required), .not-valid\\:flex:not(:valid), .not-invalid\\:flex:not(:invalid), .not-in-range\\:flex:not(:in-range), .not-out-of-range\\:flex:not(:out-of-range), .not-read-only\\:flex:not(:read-only), .not-empty\\:flex:not(:empty), .not-focus-within\\:flex:not(:focus-within), .not-hover\\:flex:not(:hover) {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
|
|
@ -1467,7 +1460,7 @@ test('not', async () => {
|
|||
}
|
||||
}
|
||||
|
||||
.not-focus\\:flex:not(:focus), .not-focus-visible\\:flex:not(:focus-visible), .not-active\\:flex:not(:active), .not-enabled\\:flex:not(:enabled), .not-disabled\\:flex:not(:disabled), .not-inert\\:flex:not([inert], [inert] *), .not-has-checked\\:flex:not(:has(:checked)), .not-aria-selected\\:flex:not([aria-selected="true"]), .not-data-foo\\:flex:not([data-foo]), .not-nth-2\\:flex:not(:nth-child(2)) {
|
||||
.not-focus\\:flex:not(:focus), .not-focus-visible\\:flex:not(:focus-visible), .not-active\\:flex:not(:active), .not-enabled\\:flex:not(:enabled), .not-disabled\\:flex:not(:disabled), .not-inert\\:flex:not(:is([inert], [inert] *)), .not-has-checked\\:flex:not(:has(:checked)), .not-aria-selected\\:flex:not([aria-selected="true"]), .not-data-foo\\:flex:not([data-foo]), .not-nth-2\\:flex:not(:nth-child(2)) {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -427,11 +427,6 @@ export function createVariants(theme: Theme): Variants {
|
|||
if (selector.includes('::')) return null
|
||||
|
||||
let selectors = segment(selector, ',').map((sel) => {
|
||||
// Remove unnecessary wrapping &:is(…) to reduce the selector size
|
||||
if (sel.startsWith('&:is(') && sel.endsWith(')')) {
|
||||
sel = sel.slice(5, -1)
|
||||
}
|
||||
|
||||
// Replace `&` in target variant with `*`, so variants like `&:hover`
|
||||
// become `&:not(*:hover)`. The `*` will often be optimized away.
|
||||
sel = sel.replaceAll('&', '*')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue