File size: 15,537 Bytes
9da4a9b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 | # 5. Cascade
The resolver flattens the nested blocks of a file into a flat list of *rules*. The cascade
then decides, per element, which declaration of each property wins. This chapter defines both.
## 5.1 The rule record
A rule is a record with these fields.
| Field | Meaning |
|---|---|
| `id` | a number, unique inside the document, in creation order |
| `sel` | the full selector text, nesting already joined (5.3) |
| `media` | a list of normalized media conditions (5.5). All must hold. |
| `signals` | a list of signal conditions (5.9). All must hold. |
| `containers` | a list of container conditions (5.6). All must hold. |
| `layer` | one of `tokens`, `base`, `components`, `utilities`, `pin` |
| `order` | a number: this rule's place in the file, counted from 0 (5.4) |
| `decls` | a list of declarations: `{ prop, value, line }` plus extras |
| `file`, `line` | where the rule came from |
| `pseudo` | the pseudo-element name for a shadow rule, else null |
| `states` | the set of state words found in `sel` (5.7) |
A *shadow rule* has the same fields. It is a rule whose `sel` names a pseudo-element target
(5.8).
## 5.2 Layers
- **CAS-1.** The layers are ranked `tokens` (0), `base` (1), `components` (2), `utilities`
(3), `pin` (4).
- **CAS-2.** Rules outside any layer block are in `components`. `@layer <name> { }` puts the
rules inside it in layer `<name>` when that is one of the five names, and in `components`
for any other name, with the warning `unknown layer "<name>", treated as "components"`. The
hint is the did-you-mean over the five names, or
`the layers are tokens, base, components, utilities, pin` when none is close. (D-3)
- **CAS-3.** `pin { }` puts its rules in layer `pin`. Declarations in `pin` behave as if
`!important`: they beat declarations from every other layer, and they beat inline styles
that the page itself wrote (5.11).
- **CAS-4.** The layer `tokens` is used only for token rules (chapter 4).
- **CAS-5.** There is no specificity. For two matching rules, a lower layer rank loses to a
higher one. Within a layer, the later `order` wins.
```dssl
.a { color: red }
pin { .a { color: blue } }
@layer base { .a { color: green } }
```
All three match. `base` is below `components`, so `red` beats `green`, and `pin` beats both.
The element is blue.
## 5.3 Nesting
A selector block may contain declarations, other selector blocks, state blocks, pseudo-element
blocks, `@media`, `@container`, `@signal`, `@layer` and `pin` blocks, and `use` lines.
- **CAS-6.** Nested selectors are joined to the parent selector. If the child contains `&`,
each `&` is replaced by the parent selector. Otherwise the result is the parent, a space
and the child (a descendant selector). A block at the top level is used as written.
- **CAS-7.** Lists are split at top-level commas (not inside parentheses or brackets, so
`:is(.x, .y)` stays whole) on both sides. Every parent part is joined with every child part,
parent-major: for each parent part in order, each child part in order. The results are
joined with `, `. So `.a, .b { .c { } }` means `.a .c, .b .c`, `.a { .c, .d { } }` means
`.a .c, .a .d`, and `&` is replaced by each parent part in turn. With no commas on either
side the result is the plain join of CAS-6. (Before 0.16.3 the lists were joined as whole
text, giving `.a, .b .c`. That was D-7.)
- **CAS-8.** A nested block whose header is a single plain word that is close to a state word
but not equal to it (`hove`, `focs`) is still a selector, but the resolver reports a
warning: `"<word>" compiled as a descendant selector`, with the hint naming the state.
This applies only when the header is a bare word of letters, digits and dashes, and there is
a parent selector. "Close" is the did-you-mean rule (9.3). The warning never fires for a
word that is an HTML element name (`li`, `td`, `menu`, and the rest of the list in the
reference engine), because a nested element name is a descendant selector on purpose. (Before
0.16.3 `li` was warned about, being two edits from `link`. That was D-8.)
- **CAS-9.** Conditions nest. `@media`, `@container` and `@signal` add one entry to the
`media`, `containers` or `signals` list of everything inside. A layer block replaces the
layer of everything inside.
- **CAS-10.** A state block adds `:<state>` to the parent selector (5.7). A pseudo-element
block makes a shadow rule (5.8).
```dssl
.card {
background: color.card
hover { background: color.paper }
@media dark { background: color.ink }
&.on { background: color.accent }
}
```
## 5.4 Order
- **CAS-11.** Each rule, shadow rule and `raw` block gets the next order number at the moment
it is emitted. The registry rule (4.3) gets 0. Token rules get a number at the moment their
block is reached.
- **CAS-12.** A rule is emitted when its block ends, or when a nested block starts, if it has
declarations waiting. So a block with declarations before and after a nested block makes
two rules, one for each fragment, in source order:
```dssl
.a {
color: red
hover { color: blue }
padding: 4
}
```
This makes `.a { color }` (order 1), `.a:hover { color }` (order 2) and `.a { padding }`
(order 3). The hover rule beats the base rule because it comes later. Writing the hover block
before `color: red` would make `color: red` win while hovering.
- **CAS-13.** A nested block is fully walked, and its own rule is emitted, before the rest of
its parent. Blocks nested inside it are emitted first.
- **CAS-14.** A rule with no declarations after resolution is not emitted.
- **CAS-15.** Declarations that appear with no selector around them are the error
`declarations must live inside a selector`, at the line of the first (line 0 for the file
top level).
- **CAS-16.** `order` is local to a file. When the host loads several files, it MUST place
them in load order and give every rule one position in that single sequence: the numbers of
a later file continue after the largest number of the earlier one. A later file wins over
an earlier one for equal layers.
Why: before 0.8.2 each file counted from zero and the numbers were compared across files,
which made a later file lose to an earlier one for no visible reason.
## 5.5 Media conditions
`@media <query>` adds one normalized query to `media`.
- **CAS-17.** The query is trimmed. An empty query is the error `@media needs a condition`
and the query `(min-width: 0)`.
- **CAS-18.** The single words `dark`, `light` and `reduced-motion` (any case) become
`(prefers-color-scheme: dark)`, `(prefers-color-scheme: light)` and
`(prefers-reduced-motion: reduce)`.
- **CAS-19.** `N` or `>= N` becomes `(min-width: Npx)`. `<= N` becomes `(max-width: Npx)`.
`A..B` becomes `(min-width: Apx) and (max-width: Bpx)`. `N`, `A` and `B` are numbers.
- **CAS-20.** Any other query is kept, except that in any comparison, a bare number after
`>`, `<` or `=` that has no unit gets `px`: `(width >= 768)` becomes `(width >= 768px)`.
- **CAS-21.** A condition holds when the host's media query evaluation says it holds.
```dssl
.a {
@media 768 { padding: 24 }
@media <= 480 { padding: 12 }
@media 480..960 { gap: 4 }
}
```
## 5.6 Container conditions
- **CAS-22.** `@container <query>` adds one normalized query (same rules as 5.5, with the
empty case `(min-width: 0)`) to `containers`.
- **CAS-23.** A container condition holds for an element when the nearest ancestor (or the
element itself) that the page marked as a container has a content width that satisfies every
bound in the query. A bound is `(min-width: N)`, `(max-width: N)`, `(width >= N)` or
`(width <= N)`. If no ancestor is a container, the condition does not hold.
## 5.7 States
- **CAS-24.** A state block `name { ... }` in a block with selector `S` makes a rule with
selector `S:name`. When `S` is a list, the state is added to every part (`.a, .b` with
`hover` is `.a:hover, .b:hover`). The state words are in appendix A. State blocks can nest
(`hover { focus { } }` gives `S:hover:focus`).
- **CAS-25.** A state block with no selector around it is the error `"<name>" needs a
selector around it`.
- **CAS-26.** All state words except `seen` are the CSS pseudo-classes of the same name and
are matched by the host.
- **CAS-27.** `seen` is not a CSS pseudo-class. An element is *seen* once at least part of it
has been inside the viewport, and stays seen. A rule whose selector contains `:seen`
matches when the selector with `:seen` removed (or `*` if nothing is left) matches and the
element is seen. An implementation MUST re-evaluate the element when it becomes seen.
- **CAS-28.** Inside a `seen` block, a value that contains `->` is the error
`"<prop>: <value>" - the -> arrow is motion syntax and does nothing inside seen { }`. The
rule is still emitted.
- **CAS-29.** `states` is the set of state words found in `sel` as `:word`, from the list:
hover, focus-visible, focus, active, disabled, checked, empty, target, invalid, valid,
required, open, closed, read-only, read-write, placeholder-shown, first-child, last-child,
only-child, seen.
## 5.8 Pseudo-elements and shadow rules
Inline styles cannot reach pseudo-elements, so the reference engine puts them in a separate
stylesheet. A native implementation has no such limit, but the resolver output still keeps
them in `shadowRules`, so that results are comparable.
- **CAS-30.** A block `::name { }` inside a selector block `S` makes a shadow rule with
`sel` = `S` and `pseudo` = `name`. When `S` is a list, there is one shadow rule per part
(and per state block inside it), each with its own copy of the declarations. At the top
level (no selector), `sel` is `*`.
- **CAS-31.** Inside a pseudo-element block, only declarations and state blocks are allowed.
Anything else is the error
`only declarations and states (hover { }) may appear inside a pseudo-element block`. A
state block inside makes a second shadow rule with `sel` = `S:<state>`. `seen` there is
the error `"seen" is engine-native and has no meaning on a pseudo-element`.
- **CAS-32.** A bare `content:` (no value) inside a pseudo-element becomes `content: ""`.
- **CAS-33.** Engine-native declarations (the `@` ones in 6.8) are not allowed on a
pseudo-element, except `@animate`. Each is the error
`"<prop>" is engine-native and has no meaning on ::<name> - a pseudo-element has no DOM
handle`, and is dropped.
- **CAS-34.** The same applies when the pseudo-element is written in the selector itself
(`.card::before { }`). The selector is split at the first top-level `::`. If every part of
a selector list has a pseudo-element, the rule becomes one shadow rule per part. If only
some do, the error is `selector list mixes pseudo-element and plain selectors: "<sel>"`
and nothing is emitted.
- **CAS-35.** A pseudo-element name MUST be in the pseudo-element list or match the vendor
pseudo pattern (appendix A), otherwise: `unknown pseudo-element "::<name>"`. `::slotted()`
is the error `::slotted() only exists inside a shadow tree, and DSSL styles the light DOM`.
`:seen` combined with a pseudo-element is an error.
- **CAS-36.** If every declaration of a shadow rule was rejected, nothing is emitted.
```dssl
.btn {
::after {
content: ""
opacity: 0
hover { opacity: 1 }
}
}
```
## 5.9 Signals
A signal is a named value that the page sets. It is how DSSL reads state that CSS cannot see
(a theme chosen in a menu, a game screen). Chapter 11 says how a browser could expose it.
- **CAS-37.** `@signal <expr>` adds one condition to `signals`. The forms are:
| Form | Condition record |
|---|---|
| `name` | `{ name, op: 'truthy', value: null }` |
| `name == value` | `{ name, op: '==', value }` |
| `name != value` | `{ name, op: '!=', value }` |
| `name in (a, b)` | `{ name, op: 'in', value: 'a,b', values: ['a','b'] }` |
| `name not in (a, b)` | `{ name, op: 'notin', value: 'a,b', values: ['a','b'] }` |
A name matches `[A-Za-z_][\w-]*`. A value may be quoted with `"` or `'`, and the quotes are
removed. List values are trimmed, unquoted, and empty ones removed.
- **CAS-38.** An expression in none of these forms is the error
`bad @signal expression "<expr>"` and is treated as `truthy` on the whole text. An empty
list is the error `@signal <name> <in|not in> ( ) has no values`.
- **CAS-39.** A signal's value is looked up on the element, then on each ancestor, then at the
document level. The first place that has the name wins. Values are compared as strings.
- **CAS-40.** A signal condition holds when:
- `truthy`: the name has a value that is not `false`, `null`, the string `false`, `0` or
the string `0`,
- `==`: the name has a value and its string equals the condition's value,
- `!=`: the name has no value, or its string differs,
- `in`: the name has a value and it is in the list,
- `notin`: the name has no value, or it is not in the list.
## 5.10 Which rules apply to an element
- **CAS-41.** A rule applies to an element when all of these hold:
1. its selector matches the element. A rule with selector `:root` applies only to the
document's root element;
2. if the selector contains `:seen`, CAS-27 holds;
3. every `media` condition holds;
4. every `signals` condition holds;
5. every `containers` condition holds.
- **CAS-42.** Applicable rules are sorted by layer rank, then by `order` (both ascending). The
declarations of the sorted rules are flattened in that order, each rule's declarations in
their written order.
- **CAS-43.** For each property, the last declaration in that sequence wins. Engine-native
declarations (6.8) each have their own combination rule, given with their feature.
## 5.11 What else decides the value
These rules are about where DSSL meets the page.
- **CAS-44.** *Inline styles written by the page.* A declaration that the page itself put in
the element's `style` attribute, or set from a script, beats any rule that is not in `pin`.
A `pin` rule beats it. A property the page removes is free for rules again.
- **CAS-45.** *Shorthand and longhand.* A shorthand expands into its longhands in cascade
position. Of a shorthand and one of its longhands, the one that comes later in the
flattened sequence wins for that longhand. This holds for `border` and its `-color`,
`-width` and `-style` longhands, and for `padding`, `margin` and `gap` and their sides.
- **CAS-46.** *Handing back with yield.* After the sequence is flattened, properties named by
the last applicable `yield:` (6.7) are removed from the DSSL result, so ordinary CSS decides
them. `yield: none` clears the list.
- **CAS-47.** *Alignment makes a box.* If the winning declarations include any of
`place-items`, `place-content`, `align-items`, `justify-items`, `align-content` or
`justify-content`, and the element has no declared `display`, and the element's natural
`display` is not `flex`, `inline-flex`, `grid` or `inline-grid` (and is not `none` or
`contents`), then `display` is set to `inline-grid` when the natural `display` is `inline` or
starts with `inline-`, and to `grid` otherwise. An explicit `display` from any rule wins over
this.
Why: alignment properties do nothing on a plain `span` or `button`, and DSSL treats that as a
silent failure.
- **CAS-48.** *Border and positional shorthands in the inline lane.* A browser that computes
styles natively does not need extra rules for CAS-45: the CSS cascade already does it.
|