Re: [csswg-drafts] Mixing :is() (or equivalent) with pseudo-elements (#9702)

Yeah, I'll give it some more time to percolate, but some variant on my "Second Try" idea is probably what I'm aiming for. Pulling it out to make it easier to find/reference:

Problem statement: we'd like the following to be valid:

```css
.foo {
 &, &::before, &::after {
  &:hover {
   color: blue;
}}}
```

...and have the obvious result, equivalent to:

```css
.foo:hover, .foo::before:hover, .foo::after::hover {
 color: blue;
}
```

Solution: introduce a new combinator, `=`, and a new `<complex-selector-unit>`, the parenthesized list.

1. `=` selector is a no-op combinator; it has no effect on the current set of matched elements. So `.foo = .bar` is the same as `.foo.bar`, `.foo = ::before` is the same as `.foo::before`, etc.
2. `<complex-selector-unit>` currently expressed the `compound selector + pseudo-element" bit of the grammar. It both filters the current matched elements *and* possibly changes the set (just to pseudos, currently), and might do that repeatedly. We add `(<selector-list>)` to the grammar for it.
 * We just run the selector list on the current set of matched elements, filtering it and potentially changing it (just to pseudos, currently) 
 * The problem statement above could be written as `.foo = (*, ::before, ::after) = :hover {...}`.
    * This works with other combinators, too: `.foo > (*, ::before)` selects all children of a `.foo` element *and* their before pseudos.
    * The selectors inside the `()` can be full complex selectors, just like in `:is()`, so `.foo = (.bar *, #baz > *)` selects `.foo` elements that have a `.bar` ancestor or a `#baz` parent.
    * This is still slightly weaker than Nesting itself; you can't do the equivalent of `.foo { &, & > .bar {...}}` because the current match set (whatever the preceding combinator yielded) is always the subject of the selectors in the paren list.  That's probably okay. We'd have to invent a new way to refer to the elements being matched if we wanted to expand that (not `:scope` or `&`, but a secret third way). I dunno, maybe we do need it.
    * Specificity would have to be the same as `:is()`, to avoid the same sort of "which way did it match" exponential explosion.

Nesting can then define itself on top of this: `X { Y {...}}` desugars to `(X) = (Y) {...}`, with just a little extra magic:
 * `&` refers to the current matched elements (so in the `(Y)` bit, `&` refers to the `(X)` elements, etc). (This expands lists' ability to change the set from just changing to pseudo-elements, to changing to anything potentially.)
 * Remember that `&` represents the elements (and now, pseudo-elements) matched by the parent selector; it's *not* textual substitution. So `::before { :hover& {...}}` is equivalent to `::before:hover`, not `:hover::before` - the parent selector filters the match set to all the ::before pseudos, then `:hover&` represents elements that are (a) in the parent's match set, and (b) hovered.
 * The final list keeps "normal" specificity behavior, where it matters which selector actually matched. (This avoids changing the *current* behavior of `.foo, { .bar, #baz {...}}`.) 

-- 
GitHub Notification of comment by tabatkins
Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/9702#issuecomment-1853046171 using your GitHub account


-- 
Sent via github-notify-ml as configured in https://github.com/w3c/github-notify-ml-config

Received on Tuesday, 12 December 2023 23:54:09 UTC