[csswg-drafts] [css-nesting-1] & representing parent elements vs parent selector (#8310)

tabatkins has just created a new issue for https://github.com/w3c/csswg-drafts:

== [css-nesting-1] & representing parent elements vs parent selector ==
When I wrote the Nesting spec originally, I specifically defined it so that `&` was an ordinary selector, that happened to match the elements matched by the parent rule. This leveraged the powers that only a live browser could have, which seemed useful. Preprocessors, unable to do the same, instead have to make their "nesting selector" more of a macro, that is substituted with the parent *selector*.

That is, in a rule like

```css
.a .b {
  .c & {...}
}
```

In the Nesting spec, the `&` matches "a .b element with a .a ancestor", and the nested rule imposes an additional "...and has a .c ancestor", so it's the equivalent of `.c :is(.a .b)`. It'll match all the following markup structures:

```html
<div class=a>
 <div class=c>
  <span class=b></span>
 </div>
</div>

<div class=c>
 <div class=a>
  <span class=b></span>
 </div>
</div>

<div class="a c">
 <span class=b></span>
</div>
```

Preprocessors can't do this (or rather, couldn't do this before `:is()` was standardized, and still can't *widely* do it since support for `:is()` is still in the low 90%s). Expanding such a selector fully (to `.a .c .b, .c .a .b, .a.c .b`) would mean a combinatorial explosion in selector length, and gets drastically worse the more combinators are in either selector. (Sass has a similar situation with its `@extend` rule, where it uses heuristics to select only a few likely expansions, rather than all possible ones.) Instead, they substitute the selector directly, giving `.c .a .b`, and only matching the second of those three markup structures.

I was discussing Nesting with @nex3 a few days ago, and she expressed surprise and some dismay that Nesting had this behavior. She believes that a large amount of CSS written with Sass nesting *expects* and *depends on* the current Sass behavior, not accidentally but quite intentionally - they expect this sort of nesting pattern to be expressing "and then a .c container around the previous stuff" rather than just "and then a .c container somewhere around the specific previous elements". She believes this would actually be a significant burden to people migrating from preprocessor nesting to native nesting, and even for people who are using native Nesting fresh, believes it'll be a common point of confusion for people using this pattern.

As well, under Sass's behavior, an author can recover the current spec's behavior by wrapping the & in an `:is()`, like `.c :is(&)` (or wrapping the parent selector). However, there is no way to recover Sass's behavior from the current spec's behavior.

In the majority of cases it doesn't make a difference which way we go. The most common relative selector case (a nested selector like `.c` or `+ .c`) is unchanged, and many non-relative selectors are also unchanged, such as `& + &` or `:not(&)`. Only cases like the above, where there is a descendant combinator in the parent selector *and* a descendant combinator in the nested selector with an `&` following it, will change their behavior.

I know this is a pretty significant semantic change to be coming at this late time, but Natalie felt it was pretty important, and @mirisuzanne backed her up, and the arguments were reasonably convincing to me.

(Note: we'd still not be doing selector concatenation - `.foo { &bar {...}}` is still equivalent to `bar.foo`, not `.foobar`.)

Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/8310 using your GitHub account


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

Received on Friday, 13 January 2023 18:43:05 UTC