Improve style invalidation performance of group-* and peer-* variants (#20513)
# For humans, by @RobinMalfait
## TL;DR
Some people ran into performance issues with the current `group-*`
variant because of the `*` inside of the selector. Take the
`group-focus-visible:flex` class for example, this generates:
```css
.group-focus-visible\:flex:is(:where(.group):focus-visible *) {
display: flex;
}
```
This slightly rewrites the selector by maintaining the same
functionality, but increasing performance because the browser has to do
fewer style recalculations.
That same class now produces:
```css
:is(:where(.group):focus-visible .group-focus-visible\\:flex) {
display: flex;
}
```
# For AI, generated by AI
This PR changes the selectors generated for `group-*` and `peer-*`
variants so browsers do less style recalculation when a group or peer
changes state (e.g. on focus or hover). Which elements match, and with
what specificity, stays the same, except for one deliberately accepted
edge case involving `@namespace` (see below).
```css
/* Before */
.group-focus\:flex:is(:where(.group):focus *) { display: flex; }
.peer-focus\:flex:is(:where(.peer):focus ~ *) { display: flex; }
/* After */
:is(:where(.group):focus .group-focus\:flex) { display: flex; }
:is(:where(.peer):focus ~ .peer-focus\:flex) { display: flex; }
```
## Why
In the old form, the subject inside `:is(…)` is `*`. When `.group`
changes state, Chromium has to recalculate styles for **every**
descendant of the group (or every following sibling of the peer), not
just the elements that use the utility. With the target itself in that
position, the browser can narrow invalidation down to elements matching
the target.
## Performance
Synthetic benchmark: toggle focus on a group/peer, flush style after
each change, and measure only a non-layout property (`outline-color`) to
isolate invalidation. Apple M5 Pro, macOS arm64.
**Elements recalculated per focus/blur** (Chromium 151,
`UpdateLayoutTree` `elementCount`, including the focused element):
| Scenario | Before | After |
| ------------------------------------------- | -----: | ------: |
| Group: 100 targets among 10,000 descendants | 10,001 | **101** |
| Peer: 20 targets among 2,000 siblings | 2,001 | **21** |
**Median time per focus/blur** (sparse: 1% of elements carry the
utility):
| Engine | Group: before → after | Peer: before → after |
| ------------ | ------------------------- | ------------------------- |
| Chromium 151 | 2.169 → **0.130 ms** (~17×) | 0.635 → **0.192 ms**
(~3.3×) |
| Firefox 153 | 0.500 → 0.350 ms | 0.650 → 0.600 ms |
| WebKit 26.5 | 0.750 → 0.750 ms | 0.450 → 0.450 ms |
**Dense case** (every element carries the utility): no meaningful
difference in any engine, because every element needs recalculation
anyway. Firefox's dense group case was ~5% slower (2.775 → 2.925 ms).
Everything else was within noise.
| Engine | Group dense: before → after | Peer dense: before → after |
| ------------ | --------------------------- |
-------------------------- |
| Chromium 151 | 4.027 → 3.971 ms | 18.095 → 18.121 ms |
| Firefox 153 | 2.775 → 2.925 ms | 43.900 → 44.025 ms |
| WebKit 26.5 | 7.550 → 7.425 ms | 35.325 → 35.100 ms |
These are micro-benchmarks of style updates, not page-load or frame-rate
numbers. The real-world gain depends on DOM size and how many elements
inside a group/peer use the variant. The biggest win is the common case:
a large group containing only a handful of `group-*` targets.
## Do the selectors behave the same?
Yes. For a group condition `G` and a target `&`:
- **Before:** matches `&` and has an ancestor matching `G`
- **After:** has an ancestor matching `G` and matches `&`
`peer-*` follows the same reasoning with `~`. Details:
- **Specificity is unchanged.** `:is()` takes the specificity of its
argument, so before was `spec(&) + spec(G)` and after is `spec(G) +
spec(&)`.
- **`&` is used, not the utility class**, so `@apply`, `@variant`,
`*:group-*`, `[&_p]:group-*`, and other variants that change the target
keep working. Complex parents such as `.foo .bar { @apply
peer-focus:flex }` keep `:is(…)` semantics during nesting: `:is(P ~
:is(.foo .bar))`.
- **`&` appears only once**, so stacked variants grow the selector
linearly. A unit test with 12 stacked variants guards against
exponential growth.
- **The selectors are also shorter:** 6 bytes of wrapping instead of 7.
- **The outer `:is(…)`** keeps compound variants such as `has-group-*`,
`not-group-*`, and `in-group-*` equivalent. For example, `has-group-*`
can still match when the group sits outside the element carrying the
utility.
**Accepted edge case:** if a stylesheet declares a default `@namespace`,
the old trailing `*` limited matches to elements in that namespace.
Inside compound variants such as `group-group-*` or `has-group-*`, the
new selector no longer does, so an SVG element (e.g. inside
`foreignObject`) can now count as the inner group. Appending `:is(*)` to
the target would restore the old behavior with no performance cost, but
it makes every selector longer for a combination (`@namespace` + mixed
namespaces + compound group variants) that is very unlikely in practice.
We can add it back if anyone runs into this.
There's one known browser quirk this PR doesn't change: Chromium doesn't
invalidate `has-group-*` when the focused group is an ancestor *outside*
the element. That happens with both the old and new selectors.
## Test plan
- [x] Updated unit test snapshots for the new selector shape
- [x] New unit test that bounds selector size with 12 stacked variants
- [x] New browser tests in `packages/tailwindcss/tests/ui.spec.ts`, run
in Chromium, Firefox, and WebKit. They cover `group-*`/`peer-*` focus
and blur, `@apply` inside a complex selector, specificity, stacked
groups in either order, and compound `group-peer-*`/`peer-group-*`. The
pre-PR selectors pass all of them too, so behavior is unchanged
- [x] `pnpm run test` and `pnpm run test:ui` pass
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9798a8ab5b
commit
fa81d697fe
8 changed files with 226 additions and 91 deletions
|
|
@ -35,6 +35,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Don't generate CSS for candidates containing an empty additional modifier (e.g. `bg-red-500/50/` and `group-hover/foo//bar:flex`) ([#20466](https://github.com/tailwindlabs/tailwindcss/pull/20466))
|
||||
- Sort `min-*`, `max-*`, and container query variants with decimal values numerically (e.g. `min-[40.25rem]` before `min-[40.5rem]`) ([#20512](https://github.com/tailwindlabs/tailwindcss/pull/20512))
|
||||
- Ensure CSS comments ending with `\*/` are closed correctly instead of swallowing the CSS that follows (e.g. `/* C:\temp\*/`) ([#20508](https://github.com/tailwindlabs/tailwindcss/pull/20508))
|
||||
- Improve style invalidation performance of `group-*` and `peer-*` variants ([#20513](https://github.com/tailwindlabs/tailwindcss/pull/20513))
|
||||
|
||||
## [4.3.3] - 2026-07-16
|
||||
|
||||
|
|
|
|||
|
|
@ -1630,7 +1630,7 @@ describe('addVariant', () => {
|
|||
).toMatchInlineSnapshot(`
|
||||
"
|
||||
@layer utilities {
|
||||
.group-hocus\\:flex:is(:is(:where(.group):hover, :where(.group):focus) *) {
|
||||
:is(:is(:where(.group):hover, :where(.group):focus) .group-hocus\\:flex) {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
|
|
@ -1744,7 +1744,7 @@ describe('addVariant', () => {
|
|||
).toMatchInlineSnapshot(`
|
||||
"
|
||||
@layer utilities {
|
||||
.group-hocus\\:flex:is(:where(.group):hover *), .group-hocus\\:flex:is(:where(.group):focus *) {
|
||||
:is(:where(.group):hover .group-hocus\\:flex), :is(:where(.group):focus .group-hocus\\:flex) {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
|
|
@ -1784,7 +1784,7 @@ describe('addVariant', () => {
|
|||
).toMatchInlineSnapshot(`
|
||||
"
|
||||
@layer utilities {
|
||||
.group-hocus\\:flex:is(:where(.group):hover *), .group-hocus\\:flex:is(:where(.group):focus *) {
|
||||
:is(:where(.group):hover .group-hocus\\:flex), :is(:where(.group):focus .group-hocus\\:flex) {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
|
|
@ -1827,12 +1827,12 @@ describe('addVariant', () => {
|
|||
"
|
||||
@layer utilities {
|
||||
@media (hover: hover) {
|
||||
.group-hocus\\:flex:is(:where(.group):hover *) {
|
||||
:is(:where(.group):hover .group-hocus\\:flex) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.group-hocus\\:flex:is(:where(.group):focus *) {
|
||||
:is(:where(.group):focus .group-hocus\\:flex) {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
|
|
@ -1964,7 +1964,7 @@ describe('addVariant', () => {
|
|||
).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 {
|
||||
:is(:where(.group):optional .group-optional\\:flex), :is(:where(.group\\/foo):optional .group-optional\\/foo\\:flex), :is(:where(.peer):optional ~ .peer-optional\\:flex), .optional\\:flex:optional {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
|
@ -2903,7 +2903,7 @@ describe('matchVariant', () => {
|
|||
).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 {
|
||||
:is(:where(.group):optional:has(test) :where(.group) .group-optional-\\[test\\]\\:flex), :is(:where(.group\\/foo):optional:has(test) :where(.group\\/foo) .group-optional-\\[test\\]\\/foo\\:flex), :is(:where(.peer):optional:has(test) :where(.peer) ~ .peer-optional-\\[test\\]\\:flex), .optional-\\[test\\]\\:flex:optional:has(test) .optional-\\[test\\]\\:flex {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1444,42 +1444,42 @@ describe('sorting', () => {
|
|||
).toMatchInlineSnapshot(`
|
||||
"
|
||||
@media (hover: hover) {
|
||||
.group-hover\\:flex:is(:where(.group):hover *) {
|
||||
:is(:where(.group):hover .group-hover\\:flex) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.group-focus\\:flex:is(:where(.group):focus *) {
|
||||
:is(:where(.group):focus .group-focus\\:flex) {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.peer-hover\\:flex:is(:where(.peer):hover ~ *) {
|
||||
:is(:where(.peer):hover ~ .peer-hover\\:flex) {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.group-hover\\:peer-hover\\:flex:is(:where(.group):hover *):is(:where(.peer):hover ~ *), .peer-hover\\:group-hover\\:flex:is(:where(.peer):hover ~ *):is(:where(.group):hover *) {
|
||||
:is(:where(.peer):hover ~ :is(:where(.group):hover .group-hover\\:peer-hover\\:flex)), :is(:where(.group):hover :is(:where(.peer):hover ~ .peer-hover\\:group-hover\\:flex)) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.group-focus\\:peer-hover\\:flex:is(:where(.group):focus *):is(:where(.peer):hover ~ *), .peer-hover\\:group-focus\\:flex:is(:where(.peer):hover ~ *):is(:where(.group):focus *) {
|
||||
:is(:where(.peer):hover ~ :is(:where(.group):focus .group-focus\\:peer-hover\\:flex)), :is(:where(.group):focus :is(:where(.peer):hover ~ .peer-hover\\:group-focus\\:flex)) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.peer-focus\\:flex:is(:where(.peer):focus ~ *) {
|
||||
:is(:where(.peer):focus ~ .peer-focus\\:flex) {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.group-hover\\:peer-focus\\:flex:is(:where(.group):hover *):is(:where(.peer):focus ~ *), .peer-focus\\:group-hover\\:flex:is(:where(.peer):focus ~ *):is(:where(.group):hover *) {
|
||||
:is(:where(.peer):focus ~ :is(:where(.group):hover .group-hover\\:peer-focus\\:flex)), :is(:where(.group):hover :is(:where(.peer):focus ~ .peer-focus\\:group-hover\\:flex)) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.group-focus\\:peer-focus\\:flex:is(:where(.group):focus *):is(:where(.peer):focus ~ *), .peer-focus\\:group-focus\\:flex:is(:where(.peer):focus ~ *):is(:where(.group):focus *) {
|
||||
:is(:where(.peer):focus ~ :is(:where(.group):focus .group-focus\\:peer-focus\\:flex)), :is(:where(.group):focus :is(:where(.peer):focus ~ .peer-focus\\:group-focus\\:flex)) {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
|
|
@ -3601,7 +3601,7 @@ describe('plugins', () => {
|
|||
).toMatchInlineSnapshot(`
|
||||
"
|
||||
@layer utilities {
|
||||
.group-hocus\\:flex:is(:is(:where(.group):hover, :where(.group):focus) *) {
|
||||
:is(:is(:where(.group):hover, :where(.group):focus) .group-hocus\\:flex) {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
|
|
@ -3636,7 +3636,7 @@ describe('plugins', () => {
|
|||
).toMatchInlineSnapshot(`
|
||||
"
|
||||
@layer utilities {
|
||||
.group-hocus\\:flex:is(:where(.group):hover *), .group-hocus\\:flex:is(:where(.group):focus *) {
|
||||
:is(:where(.group):hover .group-hocus\\:flex), :is(:where(.group):focus .group-hocus\\:flex) {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
|
|
@ -3674,7 +3674,7 @@ describe('plugins', () => {
|
|||
).toMatchInlineSnapshot(`
|
||||
"
|
||||
@layer utilities {
|
||||
.group-hocus\\:flex:is(:where(.group):hover *), .group-hocus\\:flex:is(:where(.group):focus *) {
|
||||
:is(:where(.group):hover .group-hocus\\:flex), :is(:where(.group):focus .group-hocus\\:flex) {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
|
|
@ -3715,12 +3715,12 @@ describe('plugins', () => {
|
|||
"
|
||||
@layer utilities {
|
||||
@media (hover: hover) {
|
||||
.group-hocus\\:flex:is(:where(.group):hover *) {
|
||||
:is(:where(.group):hover .group-hocus\\:flex) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.group-hocus\\:flex:is(:where(.group):focus *) {
|
||||
:is(:where(.group):focus .group-hocus\\:flex) {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
|
|
@ -4199,7 +4199,7 @@ describe('@custom-variant', () => {
|
|||
).toMatchInlineSnapshot(`
|
||||
"
|
||||
@layer utilities {
|
||||
.group-hocus\\:flex:is(:is(:where(.group):hover, :where(.group):focus) *) {
|
||||
:is(:is(:where(.group):hover, :where(.group):focus) .group-hocus\\:flex) {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
|
|
@ -4294,7 +4294,7 @@ describe('@custom-variant', () => {
|
|||
).toMatchInlineSnapshot(`
|
||||
"
|
||||
@layer utilities {
|
||||
.group-selected\\:underline:is(:where(.group)[data-selected] *), .selected\\:underline[data-selected] {
|
||||
:is(:where(.group)[data-selected] .group-selected\\:underline), .selected\\:underline[data-selected] {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
}
|
||||
|
|
@ -4322,7 +4322,7 @@ describe('@custom-variant', () => {
|
|||
).toMatchInlineSnapshot(`
|
||||
"
|
||||
@layer utilities {
|
||||
.group-hocus\\:underline:is(:is(:where(.group):hover, :where(.group):focus) *), .hocus\\:underline:hover, .hocus\\:underline:focus {
|
||||
:is(:is(:where(.group):hover, :where(.group):focus) .group-hocus\\:underline), .hocus\\:underline:hover, .hocus\\:underline:focus {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
}
|
||||
|
|
@ -4353,7 +4353,7 @@ describe('@custom-variant', () => {
|
|||
).toMatchInlineSnapshot(`
|
||||
"
|
||||
@layer utilities {
|
||||
.group-hocus\\:underline:is(:where(.group):hover *), .group-hocus\\:underline:is(:where(.group):focus *), .hocus\\:underline:hover, .hocus\\:underline:focus {
|
||||
:is(:where(.group):hover .group-hocus\\:underline), :is(:where(.group):focus .group-hocus\\:underline), .hocus\\:underline:hover, .hocus\\:underline:focus {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
}
|
||||
|
|
@ -4458,12 +4458,12 @@ describe('@custom-variant', () => {
|
|||
"
|
||||
@layer utilities {
|
||||
@media (hover: hover) {
|
||||
.group-hocus\\:underline:is(:where(.group):hover *) {
|
||||
:is(:where(.group):hover .group-hocus\\:underline) {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.group-hocus\\:underline:is(:where(.group):focus *) {
|
||||
:is(:where(.group):focus .group-hocus\\:underline) {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
|
||||
|
|
@ -4503,7 +4503,7 @@ describe('@custom-variant', () => {
|
|||
"
|
||||
@layer utilities {
|
||||
@media (hover: hover) {
|
||||
.group-hocus\\:underline:is(:where(.group):hover *), .hocus\\:underline:hover {
|
||||
:is(:where(.group):hover .group-hocus\\:underline), .hocus\\:underline:hover {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,9 +97,9 @@ test('getVariants compound', async () => {
|
|||
]
|
||||
|
||||
expect(list).toEqual([
|
||||
['@media (hover: hover) { &:is(:where(.group):hover *) }'],
|
||||
['@media (hover: hover) { &:is(:where(.group\\/sidebar):hover *) }'],
|
||||
['@media (hover: hover) { &:is(:where(.group):is(:where(.group):hover *) *) }'],
|
||||
['@media (hover: hover) { :is(:where(.group):hover &) }'],
|
||||
['@media (hover: hover) { :is(:where(.group\\/sidebar):hover &) }'],
|
||||
['@media (hover: hover) { :is(:is(:where(.group):hover :where(.group)) &) }'],
|
||||
[],
|
||||
[],
|
||||
])
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ test('utilities must be prefixed', async () => {
|
|||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.tw\\:group-hover\\:flex:is(:where(.tw\\:group):hover *), .tw\\:peer-hover\\:flex:is(:where(.tw\\:peer):hover ~ *) {
|
||||
:is(:where(.tw\\:group):hover .tw\\:group-hover\\:flex), :is(:where(.tw\\:peer):hover ~ .tw\\:peer-hover\\:flex) {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -186,7 +186,7 @@ test('after', async () => {
|
|||
test('first', async () => {
|
||||
expect(await run(['first:flex', 'group-first:flex', 'peer-first:flex'])).toMatchInlineSnapshot(`
|
||||
"
|
||||
.group-first\\:flex:is(:where(.group):first-child *), .peer-first\\:flex:is(:where(.peer):first-child ~ *), .first\\:flex:first-child {
|
||||
:is(:where(.group):first-child .group-first\\:flex), :is(:where(.peer):first-child ~ .peer-first\\:flex), .first\\:flex:first-child {
|
||||
display: flex;
|
||||
}
|
||||
"
|
||||
|
|
@ -197,7 +197,7 @@ test('first', async () => {
|
|||
test('last', async () => {
|
||||
expect(await run(['last:flex', 'group-last:flex', 'peer-last:flex'])).toMatchInlineSnapshot(`
|
||||
"
|
||||
.group-last\\:flex:is(:where(.group):last-child *), .peer-last\\:flex:is(:where(.peer):last-child ~ *), .last\\:flex:last-child {
|
||||
:is(:where(.group):last-child .group-last\\:flex), :is(:where(.peer):last-child ~ .peer-last\\:flex), .last\\:flex:last-child {
|
||||
display: flex;
|
||||
}
|
||||
"
|
||||
|
|
@ -208,7 +208,7 @@ test('last', async () => {
|
|||
test('only', async () => {
|
||||
expect(await run(['only:flex', 'group-only:flex', 'peer-only:flex'])).toMatchInlineSnapshot(`
|
||||
"
|
||||
.group-only\\:flex:is(:where(.group):only-child *), .peer-only\\:flex:is(:where(.peer):only-child ~ *), .only\\:flex:only-child {
|
||||
:is(:where(.group):only-child .group-only\\:flex), :is(:where(.peer):only-child ~ .peer-only\\:flex), .only\\:flex:only-child {
|
||||
display: flex;
|
||||
}
|
||||
"
|
||||
|
|
@ -219,7 +219,7 @@ test('only', async () => {
|
|||
test('odd', async () => {
|
||||
expect(await run(['odd:flex', 'group-odd:flex', 'peer-odd:flex'])).toMatchInlineSnapshot(`
|
||||
"
|
||||
.group-odd\\:flex:is(:where(.group):nth-child(odd) *), .peer-odd\\:flex:is(:where(.peer):nth-child(odd) ~ *), .odd\\:flex:nth-child(odd) {
|
||||
:is(:where(.group):nth-child(odd) .group-odd\\:flex), :is(:where(.peer):nth-child(odd) ~ .peer-odd\\:flex), .odd\\:flex:nth-child(odd) {
|
||||
display: flex;
|
||||
}
|
||||
"
|
||||
|
|
@ -230,7 +230,7 @@ test('odd', async () => {
|
|||
test('even', async () => {
|
||||
expect(await run(['even:flex', 'group-even:flex', 'peer-even:flex'])).toMatchInlineSnapshot(`
|
||||
"
|
||||
.group-even\\:flex:is(:where(.group):nth-child(2n) *), .peer-even\\:flex:is(:where(.peer):nth-child(2n) ~ *), .even\\:flex:nth-child(2n) {
|
||||
:is(:where(.group):nth-child(2n) .group-even\\:flex), :is(:where(.peer):nth-child(2n) ~ .peer-even\\:flex), .even\\:flex:nth-child(2n) {
|
||||
display: flex;
|
||||
}
|
||||
"
|
||||
|
|
@ -242,7 +242,7 @@ test('first-of-type', async () => {
|
|||
expect(await run(['first-of-type:flex', 'group-first-of-type:flex', 'peer-first-of-type:flex']))
|
||||
.toMatchInlineSnapshot(`
|
||||
"
|
||||
.group-first-of-type\\:flex:is(:where(.group):first-of-type *), .peer-first-of-type\\:flex:is(:where(.peer):first-of-type ~ *), .first-of-type\\:flex:first-of-type {
|
||||
:is(:where(.group):first-of-type .group-first-of-type\\:flex), :is(:where(.peer):first-of-type ~ .peer-first-of-type\\:flex), .first-of-type\\:flex:first-of-type {
|
||||
display: flex;
|
||||
}
|
||||
"
|
||||
|
|
@ -254,7 +254,7 @@ test('last-of-type', async () => {
|
|||
expect(await run(['last-of-type:flex', 'group-last-of-type:flex', 'peer-last-of-type:flex']))
|
||||
.toMatchInlineSnapshot(`
|
||||
"
|
||||
.group-last-of-type\\:flex:is(:where(.group):last-of-type *), .peer-last-of-type\\:flex:is(:where(.peer):last-of-type ~ *), .last-of-type\\:flex:last-of-type {
|
||||
:is(:where(.group):last-of-type .group-last-of-type\\:flex), :is(:where(.peer):last-of-type ~ .peer-last-of-type\\:flex), .last-of-type\\:flex:last-of-type {
|
||||
display: flex;
|
||||
}
|
||||
"
|
||||
|
|
@ -266,7 +266,7 @@ test('only-of-type', async () => {
|
|||
expect(await run(['only-of-type:flex', 'group-only-of-type:flex', 'peer-only-of-type:flex']))
|
||||
.toMatchInlineSnapshot(`
|
||||
"
|
||||
.group-only-of-type\\:flex:is(:where(.group):only-of-type *), .peer-only-of-type\\:flex:is(:where(.peer):only-of-type ~ *), .only-of-type\\:flex:only-of-type {
|
||||
:is(:where(.group):only-of-type .group-only-of-type\\:flex), :is(:where(.peer):only-of-type ~ .peer-only-of-type\\:flex), .only-of-type\\:flex:only-of-type {
|
||||
display: flex;
|
||||
}
|
||||
"
|
||||
|
|
@ -278,7 +278,7 @@ test('visited', async () => {
|
|||
expect(await run(['visited:flex', 'group-visited:flex', 'peer-visited:flex']))
|
||||
.toMatchInlineSnapshot(`
|
||||
"
|
||||
.group-visited\\:flex:is(:where(.group):visited *), .peer-visited\\:flex:is(:where(.peer):visited ~ *), .visited\\:flex:visited {
|
||||
:is(:where(.group):visited .group-visited\\:flex), :is(:where(.peer):visited ~ .peer-visited\\:flex), .visited\\:flex:visited {
|
||||
display: flex;
|
||||
}
|
||||
"
|
||||
|
|
@ -290,7 +290,7 @@ test('target', async () => {
|
|||
expect(await run(['target:flex', 'group-target:flex', 'peer-target:flex']))
|
||||
.toMatchInlineSnapshot(`
|
||||
"
|
||||
.group-target\\:flex:is(:where(.group):target *), .peer-target\\:flex:is(:where(.peer):target ~ *), .target\\:flex:target {
|
||||
:is(:where(.group):target .group-target\\:flex), :is(:where(.peer):target ~ .peer-target\\:flex), .target\\:flex:target {
|
||||
display: flex;
|
||||
}
|
||||
"
|
||||
|
|
@ -302,7 +302,7 @@ test('open', async () => {
|
|||
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) {
|
||||
.not-open\\:flex:not(:is([open], :popover-open, :open)), :is(:where(.group):is([open], :popover-open, :open) .group-open\\:flex), :is(:where(.peer):is([open], :popover-open, :open) ~ .peer-open\\:flex), .open\\:flex:is([open], :popover-open, :open) {
|
||||
display: flex;
|
||||
}
|
||||
"
|
||||
|
|
@ -314,7 +314,7 @@ test('default', async () => {
|
|||
expect(await run(['default:flex', 'group-default:flex', 'peer-default:flex']))
|
||||
.toMatchInlineSnapshot(`
|
||||
"
|
||||
.group-default\\:flex:is(:where(.group):default *), .peer-default\\:flex:is(:where(.peer):default ~ *), .default\\:flex:default {
|
||||
:is(:where(.group):default .group-default\\:flex), :is(:where(.peer):default ~ .peer-default\\:flex), .default\\:flex:default {
|
||||
display: flex;
|
||||
}
|
||||
"
|
||||
|
|
@ -326,7 +326,7 @@ test('checked', async () => {
|
|||
expect(await run(['checked:flex', 'group-checked:flex', 'peer-checked:flex']))
|
||||
.toMatchInlineSnapshot(`
|
||||
"
|
||||
.group-checked\\:flex:is(:where(.group):checked *), .peer-checked\\:flex:is(:where(.peer):checked ~ *), .checked\\:flex:checked {
|
||||
:is(:where(.group):checked .group-checked\\:flex), :is(:where(.peer):checked ~ .peer-checked\\:flex), .checked\\:flex:checked {
|
||||
display: flex;
|
||||
}
|
||||
"
|
||||
|
|
@ -338,7 +338,7 @@ test('indeterminate', async () => {
|
|||
expect(await run(['indeterminate:flex', 'group-indeterminate:flex', 'peer-indeterminate:flex']))
|
||||
.toMatchInlineSnapshot(`
|
||||
"
|
||||
.group-indeterminate\\:flex:is(:where(.group):indeterminate *), .peer-indeterminate\\:flex:is(:where(.peer):indeterminate ~ *), .indeterminate\\:flex:indeterminate {
|
||||
:is(:where(.group):indeterminate .group-indeterminate\\:flex), :is(:where(.peer):indeterminate ~ .peer-indeterminate\\:flex), .indeterminate\\:flex:indeterminate {
|
||||
display: flex;
|
||||
}
|
||||
"
|
||||
|
|
@ -355,7 +355,7 @@ test('placeholder-shown', async () => {
|
|||
]),
|
||||
).toMatchInlineSnapshot(`
|
||||
"
|
||||
.group-placeholder-shown\\:flex:is(:where(.group):placeholder-shown *), .peer-placeholder-shown\\:flex:is(:where(.peer):placeholder-shown ~ *), .placeholder-shown\\:flex:placeholder-shown {
|
||||
:is(:where(.group):placeholder-shown .group-placeholder-shown\\:flex), :is(:where(.peer):placeholder-shown ~ .peer-placeholder-shown\\:flex), .placeholder-shown\\:flex:placeholder-shown {
|
||||
display: flex;
|
||||
}
|
||||
"
|
||||
|
|
@ -367,7 +367,7 @@ test('autofill', async () => {
|
|||
expect(await run(['autofill:flex', 'group-autofill:flex', 'peer-autofill:flex']))
|
||||
.toMatchInlineSnapshot(`
|
||||
"
|
||||
.group-autofill\\:flex:is(:where(.group):autofill *), .peer-autofill\\:flex:is(:where(.peer):autofill ~ *) {
|
||||
:is(:where(.group):autofill .group-autofill\\:flex), :is(:where(.peer):autofill ~ .peer-autofill\\:flex) {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
|
|
@ -383,7 +383,7 @@ test('optional', async () => {
|
|||
expect(await run(['optional:flex', 'group-optional:flex', 'peer-optional:flex']))
|
||||
.toMatchInlineSnapshot(`
|
||||
"
|
||||
.group-optional\\:flex:is(:where(.group):optional *), .peer-optional\\:flex:is(:where(.peer):optional ~ *), .optional\\:flex:optional {
|
||||
:is(:where(.group):optional .group-optional\\:flex), :is(:where(.peer):optional ~ .peer-optional\\:flex), .optional\\:flex:optional {
|
||||
display: flex;
|
||||
}
|
||||
"
|
||||
|
|
@ -395,7 +395,7 @@ test('required', async () => {
|
|||
expect(await run(['required:flex', 'group-required:flex', 'peer-required:flex']))
|
||||
.toMatchInlineSnapshot(`
|
||||
"
|
||||
.group-required\\:flex:is(:where(.group):required *), .peer-required\\:flex:is(:where(.peer):required ~ *), .required\\:flex:required {
|
||||
:is(:where(.group):required .group-required\\:flex), :is(:where(.peer):required ~ .peer-required\\:flex), .required\\:flex:required {
|
||||
display: flex;
|
||||
}
|
||||
"
|
||||
|
|
@ -406,7 +406,7 @@ test('required', async () => {
|
|||
test('valid', async () => {
|
||||
expect(await run(['valid:flex', 'group-valid:flex', 'peer-valid:flex'])).toMatchInlineSnapshot(`
|
||||
"
|
||||
.group-valid\\:flex:is(:where(.group):valid *), .peer-valid\\:flex:is(:where(.peer):valid ~ *), .valid\\:flex:valid {
|
||||
:is(:where(.group):valid .group-valid\\:flex), :is(:where(.peer):valid ~ .peer-valid\\:flex), .valid\\:flex:valid {
|
||||
display: flex;
|
||||
}
|
||||
"
|
||||
|
|
@ -418,7 +418,7 @@ test('invalid', async () => {
|
|||
expect(await run(['invalid:flex', 'group-invalid:flex', 'peer-invalid:flex']))
|
||||
.toMatchInlineSnapshot(`
|
||||
"
|
||||
.group-invalid\\:flex:is(:where(.group):invalid *), .peer-invalid\\:flex:is(:where(.peer):invalid ~ *), .invalid\\:flex:invalid {
|
||||
:is(:where(.group):invalid .group-invalid\\:flex), :is(:where(.peer):invalid ~ .peer-invalid\\:flex), .invalid\\:flex:invalid {
|
||||
display: flex;
|
||||
}
|
||||
"
|
||||
|
|
@ -430,7 +430,7 @@ test('user-valid', async () => {
|
|||
expect(await run(['user-valid:flex', 'group-user-valid:flex', 'peer-user-valid:flex']))
|
||||
.toMatchInlineSnapshot(`
|
||||
"
|
||||
.group-user-valid\\:flex:is(:where(.group):user-valid *), .peer-user-valid\\:flex:is(:where(.peer):user-valid ~ *) {
|
||||
:is(:where(.group):user-valid .group-user-valid\\:flex), :is(:where(.peer):user-valid ~ .peer-user-valid\\:flex) {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
|
|
@ -446,7 +446,7 @@ test('user-invalid', async () => {
|
|||
expect(await run(['user-invalid:flex', 'group-user-invalid:flex', 'peer-user-invalid:flex']))
|
||||
.toMatchInlineSnapshot(`
|
||||
"
|
||||
.group-user-invalid\\:flex:is(:where(.group):user-invalid *), .peer-user-invalid\\:flex:is(:where(.peer):user-invalid ~ *) {
|
||||
:is(:where(.group):user-invalid .group-user-invalid\\:flex), :is(:where(.peer):user-invalid ~ .peer-user-invalid\\:flex) {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
|
|
@ -462,7 +462,7 @@ test('in-range', async () => {
|
|||
expect(await run(['in-range:flex', 'group-in-range:flex', 'peer-in-range:flex']))
|
||||
.toMatchInlineSnapshot(`
|
||||
"
|
||||
.group-in-range\\:flex:is(:where(.group):in-range *), .peer-in-range\\:flex:is(:where(.peer):in-range ~ *), .in-range\\:flex:in-range {
|
||||
:is(:where(.group):in-range .group-in-range\\:flex), :is(:where(.peer):in-range ~ .peer-in-range\\:flex), .in-range\\:flex:in-range {
|
||||
display: flex;
|
||||
}
|
||||
"
|
||||
|
|
@ -474,7 +474,7 @@ test('out-of-range', async () => {
|
|||
expect(await run(['out-of-range:flex', 'group-out-of-range:flex', 'peer-out-of-range:flex']))
|
||||
.toMatchInlineSnapshot(`
|
||||
"
|
||||
.group-out-of-range\\:flex:is(:where(.group):out-of-range *), .peer-out-of-range\\:flex:is(:where(.peer):out-of-range ~ *), .out-of-range\\:flex:out-of-range {
|
||||
:is(:where(.group):out-of-range .group-out-of-range\\:flex), :is(:where(.peer):out-of-range ~ .peer-out-of-range\\:flex), .out-of-range\\:flex:out-of-range {
|
||||
display: flex;
|
||||
}
|
||||
"
|
||||
|
|
@ -486,7 +486,7 @@ test('read-only', async () => {
|
|||
expect(await run(['read-only:flex', 'group-read-only:flex', 'peer-read-only:flex']))
|
||||
.toMatchInlineSnapshot(`
|
||||
"
|
||||
.group-read-only\\:flex:is(:where(.group):read-only *), .peer-read-only\\:flex:is(:where(.peer):read-only ~ *), .read-only\\:flex:read-only {
|
||||
:is(:where(.group):read-only .group-read-only\\:flex), :is(:where(.peer):read-only ~ .peer-read-only\\:flex), .read-only\\:flex:read-only {
|
||||
display: flex;
|
||||
}
|
||||
"
|
||||
|
|
@ -497,7 +497,7 @@ test('read-only', async () => {
|
|||
test('empty', async () => {
|
||||
expect(await run(['empty:flex', 'group-empty:flex', 'peer-empty:flex'])).toMatchInlineSnapshot(`
|
||||
"
|
||||
.group-empty\\:flex:is(:where(.group):empty *), .peer-empty\\:flex:is(:where(.peer):empty ~ *), .empty\\:flex:empty {
|
||||
:is(:where(.group):empty .group-empty\\:flex), :is(:where(.peer):empty ~ .peer-empty\\:flex), .empty\\:flex:empty {
|
||||
display: flex;
|
||||
}
|
||||
"
|
||||
|
|
@ -509,7 +509,7 @@ test('focus-within', async () => {
|
|||
expect(await run(['focus-within:flex', 'group-focus-within:flex', 'peer-focus-within:flex']))
|
||||
.toMatchInlineSnapshot(`
|
||||
"
|
||||
.group-focus-within\\:flex:is(:where(.group):focus-within *), .peer-focus-within\\:flex:is(:where(.peer):focus-within ~ *), .focus-within\\:flex:focus-within {
|
||||
:is(:where(.group):focus-within .group-focus-within\\:flex), :is(:where(.peer):focus-within ~ .peer-focus-within\\:flex), .focus-within\\:flex:focus-within {
|
||||
display: flex;
|
||||
}
|
||||
"
|
||||
|
|
@ -521,7 +521,7 @@ test('hover', async () => {
|
|||
expect(await run(['hover:flex', 'group-hover:flex', 'peer-hover:flex'])).toMatchInlineSnapshot(`
|
||||
"
|
||||
@media (hover: hover) {
|
||||
.group-hover\\:flex:is(:where(.group):hover *), .peer-hover\\:flex:is(:where(.peer):hover ~ *), .hover\\:flex:hover {
|
||||
:is(:where(.group):hover .group-hover\\:flex), :is(:where(.peer):hover ~ .peer-hover\\:flex), .hover\\:flex:hover {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
|
@ -533,7 +533,7 @@ test('hover', async () => {
|
|||
test('focus', async () => {
|
||||
expect(await run(['focus:flex', 'group-focus:flex', 'peer-focus:flex'])).toMatchInlineSnapshot(`
|
||||
"
|
||||
.group-focus\\:flex:is(:where(.group):focus *), .peer-focus\\:flex:is(:where(.peer):focus ~ *), .focus\\:flex:focus {
|
||||
:is(:where(.group):focus .group-focus\\:flex), :is(:where(.peer):focus ~ .peer-focus\\:flex), .focus\\:flex:focus {
|
||||
display: flex;
|
||||
}
|
||||
"
|
||||
|
|
@ -545,12 +545,12 @@ test('group-hover group-focus sorting', async () => {
|
|||
expect(await run(['group-hover:flex', 'group-focus:flex'])).toMatchInlineSnapshot(`
|
||||
"
|
||||
@media (hover: hover) {
|
||||
.group-hover\\:flex:is(:where(.group):hover *) {
|
||||
:is(:where(.group):hover .group-hover\\:flex) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.group-focus\\:flex:is(:where(.group):focus *) {
|
||||
:is(:where(.group):focus .group-focus\\:flex) {
|
||||
display: flex;
|
||||
}
|
||||
"
|
||||
|
|
@ -558,23 +558,36 @@ test('group-hover group-focus sorting', async () => {
|
|||
expect(await run(['group-focus:flex', 'group-hover:flex'])).toMatchInlineSnapshot(`
|
||||
"
|
||||
@media (hover: hover) {
|
||||
.group-hover\\:flex:is(:where(.group):hover *) {
|
||||
:is(:where(.group):hover .group-hover\\:flex) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.group-focus\\:flex:is(:where(.group):focus *) {
|
||||
:is(:where(.group):focus .group-focus\\:flex) {
|
||||
display: flex;
|
||||
}
|
||||
"
|
||||
`)
|
||||
})
|
||||
|
||||
test('stacking group and peer variants does not duplicate the target selector', async () => {
|
||||
let candidate = Array.from(
|
||||
{ length: 12 },
|
||||
(_, i) => `${i % 2 === 0 ? 'group' : 'peer'}-data-active/name${i}`,
|
||||
).join(':')
|
||||
|
||||
let output = await run([`${candidate}:flex`])
|
||||
expect(output).toContain('display: flex')
|
||||
// Repeating `&` in each variant makes the generated selector grow
|
||||
// exponentially. This many variants should still produce a small rule.
|
||||
expect(output.length).toBeLessThan(2_000)
|
||||
})
|
||||
|
||||
test('focus-visible', async () => {
|
||||
expect(await run(['focus-visible:flex', 'group-focus-visible:flex', 'peer-focus-visible:flex']))
|
||||
.toMatchInlineSnapshot(`
|
||||
"
|
||||
.group-focus-visible\\:flex:is(:where(.group):focus-visible *), .peer-focus-visible\\:flex:is(:where(.peer):focus-visible ~ *), .focus-visible\\:flex:focus-visible {
|
||||
:is(:where(.group):focus-visible .group-focus-visible\\:flex), :is(:where(.peer):focus-visible ~ .peer-focus-visible\\:flex), .focus-visible\\:flex:focus-visible {
|
||||
display: flex;
|
||||
}
|
||||
"
|
||||
|
|
@ -586,7 +599,7 @@ test('active', async () => {
|
|||
expect(await run(['active:flex', 'group-active:flex', 'peer-active:flex']))
|
||||
.toMatchInlineSnapshot(`
|
||||
"
|
||||
.group-active\\:flex:is(:where(.group):active *), .peer-active\\:flex:is(:where(.peer):active ~ *), .active\\:flex:active {
|
||||
:is(:where(.group):active .group-active\\:flex), :is(:where(.peer):active ~ .peer-active\\:flex), .active\\:flex:active {
|
||||
display: flex;
|
||||
}
|
||||
"
|
||||
|
|
@ -598,7 +611,7 @@ test('enabled', async () => {
|
|||
expect(await run(['enabled:flex', 'group-enabled:flex', 'peer-enabled:flex']))
|
||||
.toMatchInlineSnapshot(`
|
||||
"
|
||||
.group-enabled\\:flex:is(:where(.group):enabled *), .peer-enabled\\:flex:is(:where(.peer):enabled ~ *), .enabled\\:flex:enabled {
|
||||
:is(:where(.group):enabled .group-enabled\\:flex), :is(:where(.peer):enabled ~ .peer-enabled\\:flex), .enabled\\:flex:enabled {
|
||||
display: flex;
|
||||
}
|
||||
"
|
||||
|
|
@ -610,7 +623,7 @@ test('disabled', async () => {
|
|||
expect(await run(['disabled:flex', 'group-disabled:flex', 'peer-disabled:flex']))
|
||||
.toMatchInlineSnapshot(`
|
||||
"
|
||||
.group-disabled\\:flex:is(:where(.group):disabled *), .peer-disabled\\:flex:is(:where(.peer):disabled ~ *), .disabled\\:flex:disabled {
|
||||
:is(:where(.group):disabled .group-disabled\\:flex), :is(:where(.peer):disabled ~ .peer-disabled\\:flex), .disabled\\:flex:disabled {
|
||||
display: flex;
|
||||
}
|
||||
"
|
||||
|
|
@ -621,7 +634,7 @@ test('disabled', async () => {
|
|||
test('inert', async () => {
|
||||
expect(await run(['inert:flex', 'group-inert:flex', 'peer-inert:flex'])).toMatchInlineSnapshot(`
|
||||
"
|
||||
.group-inert\\:flex:is(:where(.group):is([inert], [inert] *) *), .peer-inert\\:flex:is(:where(.peer):is([inert], [inert] *) ~ *), .inert\\:flex:is([inert], [inert] *) {
|
||||
:is(:where(.group):is([inert], [inert] *) .group-inert\\:flex), :is(:where(.peer):is([inert], [inert] *) ~ .peer-inert\\:flex), .inert\\:flex:is([inert], [inert] *) {
|
||||
display: flex;
|
||||
}
|
||||
"
|
||||
|
|
@ -640,17 +653,17 @@ test('group-[...]', async () => {
|
|||
]),
|
||||
).toMatchInlineSnapshot(`
|
||||
"
|
||||
.group-\\[\\&_p\\]\\:flex:is(:where(.group) p *), .group-\\[\\&\\:hover\\]\\:group-\\[\\&_p\\]\\:flex:is(:where(.group):hover *):is(:where(.group) p *) {
|
||||
:is(:where(.group) p .group-\\[\\&_p\\]\\:flex), :is(:where(.group) p :is(:where(.group):hover .group-\\[\\&\\:hover\\]\\:group-\\[\\&_p\\]\\:flex)) {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.group-\\[\\&_p\\]\\:hover\\:flex:is(:where(.group) p *):hover, .hover\\:group-\\[\\&_p\\]\\:flex:hover:is(:where(.group) p *) {
|
||||
:is(:where(.group) p .group-\\[\\&_p\\]\\:hover\\:flex):hover, :is(:where(.group) p .hover\\:group-\\[\\&_p\\]\\:flex:hover) {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.hover\\:group-\\[\\&_p\\]\\:hover\\:flex:hover:is(:where(.group) p *):hover {
|
||||
:is(:where(.group) p .hover\\:group-\\[\\&_p\\]\\:hover\\:flex:hover):hover {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
|
@ -692,22 +705,22 @@ test('group-*', async () => {
|
|||
).toMatchInlineSnapshot(`
|
||||
"
|
||||
@media (hover: hover) {
|
||||
.group-hover\\:flex:is(:where(.group):hover *) {
|
||||
:is(:where(.group):hover .group-hover\\:flex) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.group-focus\\:flex:is(:where(.group):focus *) {
|
||||
:is(:where(.group):focus .group-focus\\:flex) {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.group-focus\\:group-hover\\:flex:is(:where(.group):focus *):is(:where(.group):hover *), .group-hover\\:group-focus\\:flex:is(:where(.group):hover *):is(:where(.group):focus *) {
|
||||
:is(:where(.group):hover :is(:where(.group):focus .group-focus\\:group-hover\\:flex)), :is(:where(.group):focus :is(:where(.group):hover .group-hover\\:group-focus\\:flex)) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.group-hocus\\:flex:is(:is(:where(.group):hover, :where(.group):focus) *) {
|
||||
:is(:is(:where(.group):hover, :where(.group):focus) .group-hocus\\:flex) {
|
||||
display: flex;
|
||||
}
|
||||
"
|
||||
|
|
@ -742,12 +755,12 @@ test('peer-[...]', async () => {
|
|||
]),
|
||||
).toMatchInlineSnapshot(`
|
||||
"
|
||||
.peer-\\[\\&_p\\]\\:flex:is(:where(.peer) p ~ *), .peer-\\[\\&\\:hover\\]\\:peer-\\[\\&_p\\]\\:flex:is(:where(.peer):hover ~ *):is(:where(.peer) p ~ *) {
|
||||
:is(:where(.peer) p ~ .peer-\\[\\&_p\\]\\:flex), :is(:where(.peer) p ~ :is(:where(.peer):hover ~ .peer-\\[\\&\\:hover\\]\\:peer-\\[\\&_p\\]\\:flex)) {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.hover\\:peer-\\[\\&_p\\]\\:flex:hover:is(:where(.peer) p ~ *), .peer-\\[\\&_p\\]\\:hover\\:flex:is(:where(.peer) p ~ *):hover, .hover\\:peer-\\[\\&_p\\]\\:focus\\:flex:hover:is(:where(.peer) p ~ *):focus {
|
||||
:is(:where(.peer) p ~ .hover\\:peer-\\[\\&_p\\]\\:flex:hover), :is(:where(.peer) p ~ .peer-\\[\\&_p\\]\\:hover\\:flex):hover, :is(:where(.peer) p ~ .hover\\:peer-\\[\\&_p\\]\\:focus\\:flex:hover):focus {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
|
@ -782,22 +795,22 @@ test('peer-*', async () => {
|
|||
).toMatchInlineSnapshot(`
|
||||
"
|
||||
@media (hover: hover) {
|
||||
.peer-hover\\:flex:is(:where(.peer):hover ~ *) {
|
||||
:is(:where(.peer):hover ~ .peer-hover\\:flex) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.peer-focus\\:flex:is(:where(.peer):focus ~ *) {
|
||||
:is(:where(.peer):focus ~ .peer-focus\\:flex) {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.peer-focus\\:peer-hover\\:flex:is(:where(.peer):focus ~ *):is(:where(.peer):hover ~ *), .peer-hover\\:peer-focus\\:flex:is(:where(.peer):hover ~ *):is(:where(.peer):focus ~ *) {
|
||||
:is(:where(.peer):hover ~ :is(:where(.peer):focus ~ .peer-focus\\:peer-hover\\:flex)), :is(:where(.peer):focus ~ :is(:where(.peer):hover ~ .peer-hover\\:peer-focus\\:flex)) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.peer-hocus\\:flex:is(:is(:where(.peer):hover, :where(.peer):focus) ~ *) {
|
||||
:is(:is(:where(.peer):hover, :where(.peer):focus) ~ .peer-hocus\\:flex) {
|
||||
display: flex;
|
||||
}
|
||||
"
|
||||
|
|
@ -1897,7 +1910,7 @@ test('not', async () => {
|
|||
}
|
||||
}
|
||||
|
||||
.group-not-checked\\:flex:is(:where(.group):not(:checked) *), .group-not-hocus\\:flex:is(:where(.group):not(:hover, :focus) *), .group-not-hocus\\/parent-name\\:flex:is(:where(.group\\/parent-name):not(:hover, :focus) *), .group-not-\\[\\:checked\\]\\:flex:is(:where(.group):not(:checked) *), .group-not-\\[\\:checked\\]\\/parent-name\\:flex:is(:where(.group\\/parent-name):not(:checked) *), .peer-not-checked\\:flex:is(:where(.peer):not(:checked) ~ *), .peer-not-hocus\\:flex:is(:where(.peer):not(:hover, :focus) ~ *), .peer-not-hocus\\/sibling-name\\:flex:is(:where(.peer\\/sibling-name):not(:hover, :focus) ~ *), .peer-not-\\[\\:checked\\]\\:flex:is(:where(.peer):not(:checked) ~ *), .peer-not-\\[\\:checked\\]\\/sibling-name\\:flex:is(:where(.peer\\/sibling-name):not(:checked) ~ *) {
|
||||
:is(:where(.group):not(:checked) .group-not-checked\\:flex), :is(:where(.group):not(:hover, :focus) .group-not-hocus\\:flex), :is(:where(.group\\/parent-name):not(:hover, :focus) .group-not-hocus\\/parent-name\\:flex), :is(:where(.group):not(:checked) .group-not-\\[\\:checked\\]\\:flex), :is(:where(.group\\/parent-name):not(:checked) .group-not-\\[\\:checked\\]\\/parent-name\\:flex), :is(:where(.peer):not(:checked) ~ .peer-not-checked\\:flex), :is(:where(.peer):not(:hover, :focus) ~ .peer-not-hocus\\:flex), :is(:where(.peer\\/sibling-name):not(:hover, :focus) ~ .peer-not-hocus\\/sibling-name\\:flex), :is(:where(.peer):not(:checked) ~ .peer-not-\\[\\:checked\\]\\:flex), :is(:where(.peer\\/sibling-name):not(:checked) ~ .peer-not-\\[\\:checked\\]\\/sibling-name\\:flex) {
|
||||
display: flex;
|
||||
}
|
||||
"
|
||||
|
|
@ -2126,7 +2139,7 @@ test('has', async () => {
|
|||
),
|
||||
).toMatchInlineSnapshot(`
|
||||
"
|
||||
.group-has-checked\\:flex:is(:where(.group):has(:checked) *), .group-has-checked\\/parent-name\\:flex:is(:where(.group\\/parent-name):has(:checked) *), .group-has-hocus\\:flex:is(:where(.group):has(:hover, :focus) *), .group-has-hocus\\/parent-name\\:flex:is(:where(.group\\/parent-name):has(:hover, :focus) *), .group-has-\\[\\:checked\\]\\:flex:is(:where(.group):has(:checked) *), .group-has-\\[\\:checked\\]\\/parent-name\\:flex:is(:where(.group\\/parent-name):has(:checked) *), .group-has-\\[\\&\\>img\\]\\:flex:is(:where(.group):has(* > img) *), .group-has-\\[\\&\\>img\\]\\/parent-name\\:flex:is(:where(.group\\/parent-name):has(* > img) *), .group-has-\\[\\+img\\]\\:flex:is(:where(.group):has( + img) *), .group-has-\\[\\>img\\]\\:flex:is(:where(.group):has( > img) *), .group-has-\\[\\>img\\]\\/parent-name\\:flex:is(:where(.group\\/parent-name):has( > img) *), .group-has-\\[\\~img\\]\\:flex:is(:where(.group):has( ~ img) *), .peer-has-checked\\:flex:is(:where(.peer):has(:checked) ~ *), .peer-has-checked\\/sibling-name\\:flex:is(:where(.peer\\/sibling-name):has(:checked) ~ *), .peer-has-hocus\\:flex:is(:where(.peer):has(:hover, :focus) ~ *), .peer-has-hocus\\/sibling-name\\:flex:is(:where(.peer\\/sibling-name):has(:hover, :focus) ~ *), .peer-has-\\[\\:checked\\]\\:flex:is(:where(.peer):has(:checked) ~ *), .peer-has-\\[\\:checked\\]\\/sibling-name\\:flex:is(:where(.peer\\/sibling-name):has(:checked) ~ *), .peer-has-\\[\\&\\>img\\]\\:flex:is(:where(.peer):has(* > img) ~ *), .peer-has-\\[\\&\\>img\\]\\/sibling-name\\:flex:is(:where(.peer\\/sibling-name):has(* > img) ~ *), .peer-has-\\[\\+img\\]\\:flex:is(:where(.peer):has( + img) ~ *), .peer-has-\\[\\>img\\]\\:flex:is(:where(.peer):has( > img) ~ *), .peer-has-\\[\\>img\\]\\/sibling-name\\:flex:is(:where(.peer\\/sibling-name):has( > img) ~ *), .peer-has-\\[\\~img\\]\\:flex:is(:where(.peer):has( ~ img) ~ *), .has-checked\\:flex:has(:checked), .has-hocus\\:flex:has(:hover, :focus), .has-\\[\\:checked\\]\\:flex:has(:checked), .has-\\[\\&\\>img\\]\\:flex:has(* > img), .has-\\[\\+img\\]\\:flex:has( + img), .has-\\[\\>img\\]\\:flex:has( > img), .has-\\[\\~img\\]\\:flex:has( ~ img) {
|
||||
:is(:where(.group):has(:checked) .group-has-checked\\:flex), :is(:where(.group\\/parent-name):has(:checked) .group-has-checked\\/parent-name\\:flex), :is(:where(.group):has(:hover, :focus) .group-has-hocus\\:flex), :is(:where(.group\\/parent-name):has(:hover, :focus) .group-has-hocus\\/parent-name\\:flex), :is(:where(.group):has(:checked) .group-has-\\[\\:checked\\]\\:flex), :is(:where(.group\\/parent-name):has(:checked) .group-has-\\[\\:checked\\]\\/parent-name\\:flex), :is(:where(.group):has(* > img) .group-has-\\[\\&\\>img\\]\\:flex), :is(:where(.group\\/parent-name):has(* > img) .group-has-\\[\\&\\>img\\]\\/parent-name\\:flex), :is(:where(.group):has( + img) .group-has-\\[\\+img\\]\\:flex), :is(:where(.group):has( > img) .group-has-\\[\\>img\\]\\:flex), :is(:where(.group\\/parent-name):has( > img) .group-has-\\[\\>img\\]\\/parent-name\\:flex), :is(:where(.group):has( ~ img) .group-has-\\[\\~img\\]\\:flex), :is(:where(.peer):has(:checked) ~ .peer-has-checked\\:flex), :is(:where(.peer\\/sibling-name):has(:checked) ~ .peer-has-checked\\/sibling-name\\:flex), :is(:where(.peer):has(:hover, :focus) ~ .peer-has-hocus\\:flex), :is(:where(.peer\\/sibling-name):has(:hover, :focus) ~ .peer-has-hocus\\/sibling-name\\:flex), :is(:where(.peer):has(:checked) ~ .peer-has-\\[\\:checked\\]\\:flex), :is(:where(.peer\\/sibling-name):has(:checked) ~ .peer-has-\\[\\:checked\\]\\/sibling-name\\:flex), :is(:where(.peer):has(* > img) ~ .peer-has-\\[\\&\\>img\\]\\:flex), :is(:where(.peer\\/sibling-name):has(* > img) ~ .peer-has-\\[\\&\\>img\\]\\/sibling-name\\:flex), :is(:where(.peer):has( + img) ~ .peer-has-\\[\\+img\\]\\:flex), :is(:where(.peer):has( > img) ~ .peer-has-\\[\\>img\\]\\:flex), :is(:where(.peer\\/sibling-name):has( > img) ~ .peer-has-\\[\\>img\\]\\/sibling-name\\:flex), :is(:where(.peer):has( ~ img) ~ .peer-has-\\[\\~img\\]\\:flex), .has-checked\\:flex:has(:checked), .has-hocus\\:flex:has(:hover, :focus), .has-\\[\\:checked\\]\\:flex:has(:checked), .has-\\[\\&\\>img\\]\\:flex:has(* > img), .has-\\[\\+img\\]\\:flex:has( + img), .has-\\[\\>img\\]\\:flex:has( > img), .has-\\[\\~img\\]\\:flex:has( ~ img) {
|
||||
display: flex;
|
||||
}
|
||||
"
|
||||
|
|
@ -2179,7 +2192,7 @@ test('aria', async () => {
|
|||
]),
|
||||
).toMatchInlineSnapshot(`
|
||||
"
|
||||
.group-aria-checked\\:flex:is(:where(.group)[aria-checked="true"] *), .group-aria-checked\\/parent-name\\:flex:is(:where(.group\\/parent-name)[aria-checked="true"] *), .group-aria-\\[modal\\]\\:flex:is(:where(.group)[aria-modal] *), .group-aria-\\[modal\\]\\/parent-name\\:flex:is(:where(.group\\/parent-name)[aria-modal] *), .group-aria-\\[valuenow\\=1\\]\\:flex:is(:where(.group)[aria-valuenow="1"] *), .group-aria-\\[valuenow\\=1\\]\\/parent-name\\:flex:is(:where(.group\\/parent-name)[aria-valuenow="1"] *), .peer-aria-checked\\:flex:is(:where(.peer)[aria-checked="true"] ~ *), .peer-aria-checked\\/sibling-name\\:flex:is(:where(.peer\\/sibling-name)[aria-checked="true"] ~ *), .peer-aria-\\[modal\\]\\:flex:is(:where(.peer)[aria-modal] ~ *), .peer-aria-\\[modal\\]\\/sibling-name\\:flex:is(:where(.peer\\/sibling-name)[aria-modal] ~ *), .peer-aria-\\[valuenow\\=1\\]\\:flex:is(:where(.peer)[aria-valuenow="1"] ~ *), .peer-aria-\\[valuenow\\=1\\]\\/sibling-name\\:flex:is(:where(.peer\\/sibling-name)[aria-valuenow="1"] ~ *), .aria-checked\\:flex[aria-checked="true"], .aria-\\[invalid\\=spelling\\]\\:flex[aria-invalid="spelling"], .aria-\\[valuenow_\\=_\\"1\\"\\]\\:flex[aria-valuenow="1"], .aria-\\[valuenow\\=1\\]\\:flex[aria-valuenow="1"] {
|
||||
:is(:where(.group)[aria-checked="true"] .group-aria-checked\\:flex), :is(:where(.group\\/parent-name)[aria-checked="true"] .group-aria-checked\\/parent-name\\:flex), :is(:where(.group)[aria-modal] .group-aria-\\[modal\\]\\:flex), :is(:where(.group\\/parent-name)[aria-modal] .group-aria-\\[modal\\]\\/parent-name\\:flex), :is(:where(.group)[aria-valuenow="1"] .group-aria-\\[valuenow\\=1\\]\\:flex), :is(:where(.group\\/parent-name)[aria-valuenow="1"] .group-aria-\\[valuenow\\=1\\]\\/parent-name\\:flex), :is(:where(.peer)[aria-checked="true"] ~ .peer-aria-checked\\:flex), :is(:where(.peer\\/sibling-name)[aria-checked="true"] ~ .peer-aria-checked\\/sibling-name\\:flex), :is(:where(.peer)[aria-modal] ~ .peer-aria-\\[modal\\]\\:flex), :is(:where(.peer\\/sibling-name)[aria-modal] ~ .peer-aria-\\[modal\\]\\/sibling-name\\:flex), :is(:where(.peer)[aria-valuenow="1"] ~ .peer-aria-\\[valuenow\\=1\\]\\:flex), :is(:where(.peer\\/sibling-name)[aria-valuenow="1"] ~ .peer-aria-\\[valuenow\\=1\\]\\/sibling-name\\:flex), .aria-checked\\:flex[aria-checked="true"], .aria-\\[invalid\\=spelling\\]\\:flex[aria-invalid="spelling"], .aria-\\[valuenow_\\=_\\"1\\"\\]\\:flex[aria-valuenow="1"], .aria-\\[valuenow\\=1\\]\\:flex[aria-valuenow="1"] {
|
||||
display: flex;
|
||||
}
|
||||
"
|
||||
|
|
@ -2218,7 +2231,7 @@ test('data', async () => {
|
|||
]),
|
||||
).toMatchInlineSnapshot(`
|
||||
"
|
||||
.group-data-\\[disabled\\]\\:flex:is(:where(.group)[data-disabled] *), .group-data-\\[disabled\\]\\/parent-name\\:flex:is(:where(.group\\/parent-name)[data-disabled] *), .group-data-\\[foo\\$\\=\\'bar\\'_i\\]\\/parent-name\\:flex:is(:where(.group\\/parent-name)[data-foo$="bar" i] *), .group-data-\\[foo\\$\\=bar_baz_i\\]\\/parent-name\\:flex:is(:where(.group\\/parent-name)[data-foo$="bar baz" i] *), .group-data-\\[foo\\=1\\]\\:flex:is(:where(.group)[data-foo="1"] *), .group-data-\\[foo\\=1\\]\\/parent-name\\:flex:is(:where(.group\\/parent-name)[data-foo="1"] *), .group-data-\\[foo\\=bar\\ baz\\]\\/parent-name\\:flex:is(:where(.group\\/parent-name)[data-foo="bar baz"] *), .peer-data-\\[disabled\\]\\:flex:is(:where(.peer)[data-disabled] ~ *), .peer-data-\\[disabled\\]\\/sibling-name\\:flex:is(:where(.peer\\/sibling-name)[data-disabled] ~ *), .peer-data-\\[foo\\$\\=\\'bar\\'_i\\]\\/sibling-name\\:flex:is(:where(.peer\\/sibling-name)[data-foo$="bar" i] ~ *), .peer-data-\\[foo\\$\\=bar_baz_i\\]\\/sibling-name\\:flex:is(:where(.peer\\/sibling-name)[data-foo$="bar baz" i] ~ *), .peer-data-\\[foo\\=1\\]\\:flex:is(:where(.peer)[data-foo="1"] ~ *), .peer-data-\\[foo\\=1\\]\\/sibling-name\\:flex:is(:where(.peer\\/sibling-name)[data-foo="1"] ~ *), .peer-data-\\[foo\\=bar\\ baz\\]\\/sibling-name\\:flex:is(:where(.peer\\/sibling-name)[data-foo="bar baz"] ~ *), .data-disabled\\:flex[data-disabled], .data-\\[foo\\$\\=\\'bar\\'_i\\]\\:flex[data-foo$="bar" i], .data-\\[foo\\$\\=bar_baz_i\\]\\:flex[data-foo$="bar baz" i], .data-\\[foo\\=1\\]\\:flex[data-foo="1"], .data-\\[foo\\=bar_baz\\]\\:flex[data-foo="bar baz"], .data-\\[potato_\\=_\\"salad\\"\\]\\:flex[data-potato="salad"], .data-\\[potato_\\^\\=_\\"salad\\"\\]\\:flex[data-potato^="salad"], .data-\\[potato\\=\\"\\^_\\=\\"\\]\\:flex[data-potato="^ ="], .data-\\[potato\\=salad\\]\\:flex[data-potato="salad"] {
|
||||
:is(:where(.group)[data-disabled] .group-data-\\[disabled\\]\\:flex), :is(:where(.group\\/parent-name)[data-disabled] .group-data-\\[disabled\\]\\/parent-name\\:flex), :is(:where(.group\\/parent-name)[data-foo$="bar" i] .group-data-\\[foo\\$\\=\\'bar\\'_i\\]\\/parent-name\\:flex), :is(:where(.group\\/parent-name)[data-foo$="bar baz" i] .group-data-\\[foo\\$\\=bar_baz_i\\]\\/parent-name\\:flex), :is(:where(.group)[data-foo="1"] .group-data-\\[foo\\=1\\]\\:flex), :is(:where(.group\\/parent-name)[data-foo="1"] .group-data-\\[foo\\=1\\]\\/parent-name\\:flex), :is(:where(.group\\/parent-name)[data-foo="bar baz"] .group-data-\\[foo\\=bar\\ baz\\]\\/parent-name\\:flex), :is(:where(.peer)[data-disabled] ~ .peer-data-\\[disabled\\]\\:flex), :is(:where(.peer\\/sibling-name)[data-disabled] ~ .peer-data-\\[disabled\\]\\/sibling-name\\:flex), :is(:where(.peer\\/sibling-name)[data-foo$="bar" i] ~ .peer-data-\\[foo\\$\\=\\'bar\\'_i\\]\\/sibling-name\\:flex), :is(:where(.peer\\/sibling-name)[data-foo$="bar baz" i] ~ .peer-data-\\[foo\\$\\=bar_baz_i\\]\\/sibling-name\\:flex), :is(:where(.peer)[data-foo="1"] ~ .peer-data-\\[foo\\=1\\]\\:flex), :is(:where(.peer\\/sibling-name)[data-foo="1"] ~ .peer-data-\\[foo\\=1\\]\\/sibling-name\\:flex), :is(:where(.peer\\/sibling-name)[data-foo="bar baz"] ~ .peer-data-\\[foo\\=bar\\ baz\\]\\/sibling-name\\:flex), .data-disabled\\:flex[data-disabled], .data-\\[foo\\$\\=\\'bar\\'_i\\]\\:flex[data-foo$="bar" i], .data-\\[foo\\$\\=bar_baz_i\\]\\:flex[data-foo$="bar baz" i], .data-\\[foo\\=1\\]\\:flex[data-foo="1"], .data-\\[foo\\=bar_baz\\]\\:flex[data-foo="bar baz"], .data-\\[potato_\\=_\\"salad\\"\\]\\:flex[data-potato="salad"], .data-\\[potato_\\^\\=_\\"salad\\"\\]\\:flex[data-potato^="salad"], .data-\\[potato\\=\\"\\^_\\=\\"\\]\\:flex[data-potato="^ ="], .data-\\[potato\\=salad\\]\\:flex[data-potato="salad"] {
|
||||
display: flex;
|
||||
}
|
||||
"
|
||||
|
|
@ -2717,7 +2730,7 @@ test('variant order', async () => {
|
|||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.group-hover\\:flex:is(:where(.group):hover *), .peer-hover\\:flex:is(:where(.peer):hover ~ *) {
|
||||
:is(:where(.group):hover .group-hover\\:flex), :is(:where(.peer):hover ~ .peer-hover\\:flex) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
|
@ -2990,7 +3003,7 @@ test('move modifier of compound variant to sub-variant if its also a compound va
|
|||
]),
|
||||
).toMatchInlineSnapshot(`
|
||||
"
|
||||
.not-group-focus\\/name\\:flex:not(:is(:where(.group\\/name):focus *)), .group-peer-focus\\/name\\:flex:is(:where(.group\\/name):is(:where(.peer):focus ~ *) *), :where(:is(:where(.group\\/name):focus *)) .in-group-focus\\/name\\:flex, .has-group-focus\\/name\\:flex:has(:is(:where(.group\\/name):focus *)) {
|
||||
.not-group-focus\\/name\\:flex:not(:is(:where(.group\\/name):focus *)), :is(:is(:where(.peer):focus ~ :where(.group\\/name)) .group-peer-focus\\/name\\:flex), :where(:is(:where(.group\\/name):focus *)) .in-group-focus\\/name\\:flex, .has-group-focus\\/name\\:flex:has(:is(:where(.group\\/name):focus *)) {
|
||||
display: flex;
|
||||
}
|
||||
"
|
||||
|
|
|
|||
|
|
@ -610,7 +610,13 @@ export function createVariants(theme: Theme): Variants {
|
|||
selector = `:is(${selector})`
|
||||
}
|
||||
|
||||
node.selector = `&:is(${selector} *)`
|
||||
// Put the target inside `:is` so browsers can limit style invalidation to
|
||||
// it when the group changes. Keep `&` instead of the candidate's class so
|
||||
// this also works with `@apply` and variants that change the target.
|
||||
//
|
||||
// The `:is` wrapper also keeps `has-group-*` from anchoring the group
|
||||
// itself to the element carrying the utility.
|
||||
node.selector = `:is(${selector} &)`
|
||||
|
||||
// Track that the variant was actually applied
|
||||
didApply = true
|
||||
|
|
@ -651,7 +657,7 @@ export function createVariants(theme: Theme): Variants {
|
|||
|
||||
// For most variants we rely entirely on CSS nesting to build-up the final
|
||||
// selector, but there is no way to use CSS nesting to make `&` refer to
|
||||
// just the `.group` class the way we'd need to for these variants, so we
|
||||
// just the `.peer` class the way we'd need to for these variants, so we
|
||||
// need to replace it in the selector ourselves.
|
||||
let selector = node.selector.replaceAll('&', variantSelector)
|
||||
|
||||
|
|
@ -662,7 +668,8 @@ export function createVariants(theme: Theme): Variants {
|
|||
selector = `:is(${selector})`
|
||||
}
|
||||
|
||||
node.selector = `&:is(${selector} ~ *)`
|
||||
// As with `group`, constrain the target without repeating its selector.
|
||||
node.selector = `:is(${selector} ~ &)`
|
||||
|
||||
// Track that the variant was actually applied
|
||||
didApply = true
|
||||
|
|
|
|||
|
|
@ -2387,6 +2387,120 @@ test('shadow DOM has access to variables', async ({ page }) => {
|
|||
}
|
||||
}
|
||||
|
||||
for (let kind of ['group', 'peer']) {
|
||||
test(`${kind}-* variants`, async ({ page }) => {
|
||||
let targets = html`
|
||||
<div id="target" class="${kind}-focus:z-10"></div>
|
||||
<div id="applied" class="applied"></div>
|
||||
`
|
||||
|
||||
let { getPropertyValue } = await render(
|
||||
page,
|
||||
kind === 'group'
|
||||
? html`<div id="trigger" tabindex="0" class="group owner">${targets}</div>`
|
||||
: html`<div class="owner">
|
||||
<div id="trigger" tabindex="0" class="peer"></div>
|
||||
${targets}
|
||||
</div>`,
|
||||
css`
|
||||
.owner > .applied {
|
||||
@apply ${kind}-focus:z-20;
|
||||
}
|
||||
`,
|
||||
)
|
||||
|
||||
expect(await getPropertyValue('#target', 'z-index')).toEqual('auto')
|
||||
expect(await getPropertyValue('#applied', 'z-index')).toEqual('auto')
|
||||
|
||||
await page.locator('#trigger').focus()
|
||||
|
||||
expect(await getPropertyValue('#target', 'z-index')).toEqual('10')
|
||||
expect(await getPropertyValue('#applied', 'z-index')).toEqual('20')
|
||||
|
||||
await page.locator('#trigger').blur()
|
||||
|
||||
expect(await getPropertyValue('#target', 'z-index')).toEqual('auto')
|
||||
expect(await getPropertyValue('#applied', 'z-index')).toEqual('auto')
|
||||
})
|
||||
}
|
||||
|
||||
test('group-* variants do not change specificity', async ({ page }) => {
|
||||
let { getPropertyValue } = await render(
|
||||
page,
|
||||
html`
|
||||
<div id="trigger" tabindex="0" class="group">
|
||||
<div id="target" class="target group-focus:z-10"></div>
|
||||
</div>
|
||||
`,
|
||||
css`
|
||||
@layer utilities {
|
||||
.target {
|
||||
z-index: 20;
|
||||
}
|
||||
.target.equal {
|
||||
z-index: 30;
|
||||
}
|
||||
}
|
||||
`,
|
||||
)
|
||||
|
||||
await page.locator('#trigger').focus()
|
||||
|
||||
expect(await getPropertyValue('#target', 'z-index')).toEqual('10')
|
||||
|
||||
await page.locator('#target').evaluate((element) => element.classList.add('equal'))
|
||||
|
||||
expect(await getPropertyValue('#target', 'z-index')).toEqual('30')
|
||||
})
|
||||
|
||||
test('stacked group-* variants match regardless of the order of the groups', async ({ page }) => {
|
||||
for (let [outer, inner] of [
|
||||
['a', 'b'],
|
||||
['b', 'a'],
|
||||
]) {
|
||||
let { getPropertyValue } = await render(
|
||||
page,
|
||||
html`
|
||||
<div class="group/${outer}" data-active>
|
||||
<div id="inner" class="group/${inner}" data-active>
|
||||
<div id="target" class="group-data-active/a:group-data-active/b:z-10"></div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
)
|
||||
|
||||
expect(await getPropertyValue('#target', 'z-index')).toEqual('10')
|
||||
|
||||
await page.locator('#inner').evaluate((element) => element.removeAttribute('data-active'))
|
||||
|
||||
expect(await getPropertyValue('#target', 'z-index')).toEqual('auto')
|
||||
}
|
||||
})
|
||||
|
||||
test('compound group-* and peer-* variants', async ({ page }) => {
|
||||
let { getPropertyValue } = await render(
|
||||
page,
|
||||
html`
|
||||
<div id="outer" tabindex="0" class="group">
|
||||
<div id="peer" tabindex="0" class="peer"></div>
|
||||
<div id="middle" class="group peer-group-focus:z-20">
|
||||
<div id="target" class="group-peer-focus:z-10"></div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
)
|
||||
|
||||
await page.locator('#peer').focus()
|
||||
|
||||
expect(await getPropertyValue('#target', 'z-index')).toEqual('10')
|
||||
expect(await getPropertyValue('#middle', 'z-index')).toEqual('auto')
|
||||
|
||||
await page.locator('#outer').focus()
|
||||
|
||||
expect(await getPropertyValue('#target', 'z-index')).toEqual('auto')
|
||||
expect(await getPropertyValue('#middle', 'z-index')).toEqual('20')
|
||||
})
|
||||
|
||||
// ---
|
||||
|
||||
const preflight = fs.readFileSync(path.resolve(__dirname, '..', 'preflight.css'), 'utf-8')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue