Skip to content

Commit

Permalink
[css-nesting] Retire @nest, introduce the 'nested declarations rule' (#…
Browse files Browse the repository at this point in the history
…10386)

Resolves #10234.
  • Loading branch information
andruud authored Jun 25, 2024
1 parent d2ca460 commit 7d8f87c
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 129 deletions.
174 changes: 50 additions & 124 deletions css-nesting-1/Overview.bs
Original file line number Diff line number Diff line change
Expand Up @@ -636,7 +636,7 @@ Nesting Other At-Rules {#conditionals}
with their [=nesting selector=] taking its definition
from the nearest ancestor [=style rule=].
* Properties can be directly used,
acting as if they were nested in an ''@nest'' rule.
acting as if they were nested in a [=nested declarations rule=].

<div class=note>
Specifically, these rules are capable of being [=nested group rules=]:
Expand Down Expand Up @@ -667,11 +667,10 @@ Nesting Other At-Rules {#conditionals}
/* equivalent to: */
.foo {
display: grid;

@media (orientation: landscape) {
@nest {
grid-auto-flow: column;
}
}
@media (orientation: landscape) {
.foo {
grid-auto-flow: column
}
}

Expand Down Expand Up @@ -754,46 +753,9 @@ Nesting Other At-Rules {#conditionals}
</div>

Runs of consecutive directly-nested properties
are automatically wrapped in ''@nest'' rules.
are automatically wrapped in [=nested declarations rules=].
(This is observable in the CSSOM.)

<div class=example>

For example, the earlier example:

<pre highlight=css>
.foo {
display: grid;

@media (orientation: landscape) {
grid-auto-flow: column;
}
}
/* equivalent to */
.foo {
display: grid;

@media (orientation: landscape) {
@nest {
grid-auto-flow: column;
}
}
}
</pre>

is in fact <em>exactly</em> equivalent,
producing the exact same CSSOM structure.
The {{CSSMediaRule}} object
will have a single {{CSSNestRule}} object
in its <code highlight=js>.childRules</code> attribute,
containing the 'grid-auto-flow' property.
</div>

Note: This does mean that the serialization of such rules will differ
from how they were originally written,
with <em>no</em> directly-nested properties in the serialization.


<h4 id=nesting-at-scope>
Nested ''@scope'' Rules</h4>

Expand Down Expand Up @@ -852,7 +814,7 @@ Mixing Nesting Rules and Declarations {#mixing}
and [=nested style rules=] or [=nested group rules=],
all three can be arbitrarily mixed.
Declarations coming after or between rules
are implicitly wrapped in ''@nest'' rules,
are implicitly wrapped in [=nested declarations rules=],
to preserve their order relative to the other rules.

<div class=example>
Expand All @@ -867,18 +829,14 @@ Mixing Nesting Rules and Declarations {#mixing}
}

/* equivalent to */
article {
color: green;
& { color: blue; }
@nest { color: red; }
}
article { color: green; }
:is(article) { color: blue; }
article { color: red; }

/* NOT equivalent to */
article {
color: green;
color: red;
& { color: blue; }
}
article { color: green; }
article { color: red; }
:is(article) { color: blue; }
</pre>
</div>

Expand Down Expand Up @@ -917,7 +875,7 @@ Mixing Nesting Rules and Declarations {#mixing}

Note: While one <em>can</em> freely intermix declarations and nested rules,
it's harder to read and somewhat confusing to do so,
since the later properties are automatically wrapped in an ''@nest'' rule
since the later properties are automatically wrapped in a [=nested declarations rule=]
that doesn't appear in the source text.
For readability's sake,
it's recommended that authors put all their properties first in a style rule,
Expand Down Expand Up @@ -1139,19 +1097,19 @@ Nesting Selector: the ''&'' selector {#nest-selector}
(that is, ''&div'' is illegal, and must be written ''div&'' instead).


<!-- Big Text: @nest
<!-- Big Text: Nested Decl

███▌ █▌ █████▌ ███▌ █████▌
█▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌
▌▐█ █▌ █▌ █▌ █▌ █▌ █▌
█▌▐█ █▌ █▌▐█ █▌ ████ ███▌ █▌
█▌ ██▌ █▌ █▌ █▌ █▌ █▌
█▌ █▌ █▌ █▌ █▌ █▌ █▌
████▌ █▌ ▐▌ █████▌ ███▌ █
█ █▌ █████▌ ███▌ █████▌ █████▌ ████▌ ███▌ █████▌ ███▌ █▌
█▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌
█▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌
█▌▐█ █▌ ████ ███▌ █▌ ███ █▌ █▌ █▌ █▌ ████ █▌ █▌
█▌ ██▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌
█▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌
█▌ ▐▌ ████▌ ███ █▌ █████▌ ████▌ ████████▌██▌ █████
-->

<h2 id=nest-rule>
The ''@nest'' Rule</h2>
<h2 id=nested-declarations-rule>
The Nested Declarations Rule</h2>

For somewhat-technical reasons,
it's important to be able to distinguish properties
Expand Down Expand Up @@ -1205,34 +1163,21 @@ The ''@nest'' Rule</h2>
These run into the same problems as above.

To address all of these issue,
the <dfn at-rule>@nest</dfn> rule is defined:

<pre class=prod>
&lt;@nest> = @nest { <<block-contents>> }
</pre>
we instead wrap runs of consecutive directly-nested properties
in a <dfn export>nested declarations rule</dfn>.

Unless otherwise specified,
an ''@nest'' rule is a [=nested style rule=],
a [=nested declarations rule=] is a [=nested style rule=],
and acts identically to any other style rule.
It matches the exact same elements and pseudo-elements
as its parent style rule,
with the same specificity behavior.
<span class=note>(This is <em>similar to</em> being a style rule with an ''&'' selector,
but slightly more powerful,
as explained above.)</span>
If it does not have a parent style rule,
it matches elements as if it were a [=style rule=] with a '':scope'' selector.

Note: While it is <em>possible</em> to manually specify an ''@nest'' rule in a stylesheet,
there's never any reason to.
The parser automatically produces them when needed.

Issue: It's possible that we might make changes to further hide the existence of ''@nest'',
such as serializing as just its declarations when possible.
See <a href="https://github.com/w3c/csswg-drafts/issues/8738#issuecomment-2061777236">Issue 8738</a>.

<details class=note>
<summary>Why does the ''@nest'' rule exist?</summary>
<summary>Why does the [=nested declarations rule=] exist?</summary>

Originally, this specification grouped all declarations in style rules together,
"moving" them from their original location
Expand All @@ -1241,15 +1186,15 @@ The ''@nest'' Rule</h2>
in plain style rules,
using the ''&'' selector.

There are two major reasons we switched to instead use the ''@nest'' rule.
There are two major reasons we switched to instead use the [=nested declarations rule=].

First, using an ''& {...}'' rule to implicitly wrap declarations in a [=nested group rule=]
also changed the behavior.
As shown in the example following this note,
it breaks cases where the parent style rule contains pseudo-elements,
and even when that's not the case,
it potentially changes the specificity behavior of the nested declarations.
Switching to ''@nest'' avoids these problems,
Switching to the [=nested declarations rule=] avoids these problems,
making the behavior of nested ''@media''/etc
identical to the behavior of *non*-nested ''@media''/etc.

Expand All @@ -1262,7 +1207,7 @@ The ''@nest'' Rule</h2>
and in order to actually make that representable in the CSSOM,
that means they have to be wrapped in some kind of rule.
The same issues as the previous paragraph apply if we just use a normal ''& {...}'' rule,
so ''@nest'' lets us do so without side effects.
so the [=nested declarations rule=] lets us do so without side effects.
</details>

<div class=example>
Expand Down Expand Up @@ -1295,14 +1240,14 @@ The ''@nest'' Rule</h2>
}
```

Then the ''color: white'' is implicitly wrapped in an ''@nest'',
Then the ''color: white'' is implicitly wrapped in a [=nested declarations rule=],
which is guaranteed to match <em>exactly</em> the same as its parent style rule,
so the element <em>and</em> its pseudo-elements
would all have white text in a darkmode page.
</div>

<div class=example>
Declarations interleaved with rules get implicitly wrapped in an ''@nest'',
Declarations interleaved with rules get implicitly wrapped in a [=nested declarations rule=],
which makes them part of a separate style rule.
For example, given this CSS:

Expand All @@ -1320,7 +1265,7 @@ The ''@nest'' Rule</h2>
the ''color: black'' one.

The ''background: silver'' declaration
will instead be found in the implicitly-created ''@nest'' child rule,
will instead be found in the implicitly-created [=nested declarations rule|nested declarations child rule=],
at <code highlight=js>fooRule.cssRules[1].style</code>.
</div>

Expand Down Expand Up @@ -1354,20 +1299,20 @@ with the implied [=nesting selector=] inserted.
will serialize as ''& > .foo''.
</div>

The {{CSSNestRule}} Interface {#the-cssnestrule}
The {{CSSNestedDeclarations}} Interface {#the-cssnestrule}
-----------------------------

The {{CSSNestRule}} interface represents an ''@nest'' rule.
The {{CSSNestedDeclarations}} interface represents a [=nested declarations rule=].

<xmp class=idl>
[Exposed=Window]
interface CSSNestRule : CSSGroupingRule {
interface CSSNestedDeclarations : CSSRule {
[SameObject, PutForwards=cssText] readonly attribute CSSStyleProperties style;
};
</xmp>

<div algorithm>
The <dfn attribute for=CSSNestRule>style</dfn> attribute
The <dfn attribute for=CSSNestedDeclarations>style</dfn> attribute
must return a {{CSSStyleProperties}} object for the rule,
with the following properties:

Expand All @@ -1383,35 +1328,13 @@ interface CSSNestRule : CSSGroupingRule {
:: Null
</div>

<div class=example>
Note that interleaved declarations,
or all declarations in [=nested group rules=],
will be implicitly wrapped in ''@nest'' rules,
which will affect the serialization.
A [=nested group rule=] like:

<pre class=lang-css>
.foo {
@media (prefers-color-scheme: dark) {
color: white;
background: black;
}
}
</pre>
The {{CSSNestedDeclarations}} rule [=serialize a CSS rule|serializes=]
as if its [=CSS declaration block|declaration block=]
had been [=serialize a CSS declaration block|serialized=] directly.

will serialize as:

<pre class=lang-css>
.foo {
@media (prefers-color-scheme: dark) {
@nest {
color: white;
background: black;
}
}
}
</pre>
</div>
Note: This means that multiple adjacent [=nested declarations rules=]
(which is possible to create with e.g. {{CSSGroupingRule/insertRule}})
will collapse into a single rule when serialized and parsed again.


<!-- Big Text: changes -->
Expand Down Expand Up @@ -1439,6 +1362,9 @@ Significant changes since the
(<a href="https://github.com/w3c/csswg-drafts/issues/9069">Issue 9069</a>)

* Declarations intermixed with rules (or all declarations in nested group rules)
are now automatically wrapped in ''@nest'' rules.
(Also the ''@nest'' rule was added.)
(<a href="https://github.com/w3c/csswg-drafts/issues/8738">Issue 8738</a>)
are now automatically wrapped in <code>@nest</code> rules.
(Also the <code>@nest</code> rule was added.)
(<a href="https://github.com/w3c/csswg-drafts/issues/8738">Issue 8738</a>)

* Replaced <code>@nest</code> with [=nested declarations rules=].
(<a href="https://github.com/w3c/csswg-drafts/issues/10234">Issue 10234</a>)
6 changes: 3 additions & 3 deletions css-syntax-3/Overview.bs
Original file line number Diff line number Diff line change
Expand Up @@ -2629,7 +2629,7 @@ Consume an at-rule</h4>
Note: If the result contains [=lists=] of [=declarations=],
how they're materialized in the CSSOM
depends on the rule.
Some turn them all into ''@nest'' rules,
Some turn them all into [=nested declarations rules=],
others will treat them all as declarations,
and others will treat the first item differently from the rest.

Expand Down Expand Up @@ -2758,7 +2758,7 @@ Consume a qualified rule</h4>
remove it from |child rules|
and assign it to |rule|'s declarations.
If any remaining items of |child rules| are [=lists=] of [=declarations=],
replace them with ''@nest'' [=at-rules=]
replace them with [=nested declarations rules=]
containing the [=list=] as its sole child.
Assign |child rules| to |rule|'s child [=rules=].

Expand Down Expand Up @@ -2802,7 +2802,7 @@ Consume a block's contents</h4>
a [=list=] of [=declarations=]
might be materialized in the CSSOM as either
a {{CSSStyleDeclaration}},
or as a {{CSSNestRule}}.
or as a {{CSSNestedDeclarations}} rule.

Let |rules| be an empty [=list=],
containing either [=rules=]
Expand Down
15 changes: 13 additions & 2 deletions cssom-1/Overview.bs
Original file line number Diff line number Diff line change
Expand Up @@ -1845,13 +1845,24 @@ To <dfn export>serialize a CSS rule</dfn>, perform one of the following in accor

Issue: The "indented by two spaces" bit matches browsers, but needs work, see <a href="https://github.com/w3c/csswg-drafts/issues/5494">#5494</a>

To <dfn export>insert a CSS rule</dfn> <var>rule</var> in a CSS rule list <var>list</var> at index <var>index</var>, follow these steps:
To <dfn export>insert a CSS rule</dfn> <var>rule</var> in a CSS rule list <var>list</var> at index <var>index</var>,
with a flag <var>nested</var>,
follow these steps:
<ol>
<li>Set <var>length</var> to the number of items in <var>list</var>.
<li>If <var>index</var> is greater than <var>length</var>, then <a>throw</a>
an {{IndexSizeError}} exception.
<li>Set <var>new rule</var> to the results of performing <a>parse a CSS rule</a>
on argument <var>rule</var>.
<li>If <var>new rule</var> is a syntax error, and <var>nested</var> is set,
perform the following substeps:
<ul>
<li> Set <var>declarations</var> to the results of performing <a>parse a CSS declaration block</a>,
on argument <var>rule</var>.
<li> If <var>declarations</var> is a syntax error, <a>throw</a> a {{SyntaxError}} exception.
<li> Otherwise, set <var>new rule</var> to a new [=nested declarations rule=]
with <var>declarations</var> as it contents.
</ul>
<li>If <var>new rule</var> is a syntax error, <a>throw</a>
a {{SyntaxError}} exception.
<li>If <var>new rule</var> cannot be inserted into <var>list</var> at the zero-index position <var>index</var> due to constraints
Expand Down Expand Up @@ -2087,7 +2098,7 @@ The <dfn attribute for=CSSGroupingRule>cssRules</dfn> attribute must return a <c

The <dfn method for=CSSGroupingRule>insertRule(<var>rule</var>, <var>index</var>)</dfn> method must return the result of
invoking <a>insert a CSS rule</a> <var>rule</var> into the <a for=CSSRule>child CSS rules</a> at
<var>index</var>.
<var>index</var>, with the <var>nested</var> flag set.

The <dfn method for=CSSGroupingRule>deleteRule(<var>index</var>)</dfn> method must <a>remove a CSS rule</a> from the
<a for=CSSRule>child CSS rules</a> at <var>index</var>.
Expand Down

0 comments on commit 7d8f87c

Please sign in to comment.