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.