@canonical/anatomy-dsl 0.2.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +240 -23
- package/definitions/lift.fixture.json +216 -0
- package/definitions/ontology.ttl +206 -13
- package/definitions/registry.ttl +659 -0
- package/definitions/shapes.ttl +593 -8
- package/definitions/style-keys.yaml +391 -0
- package/package.json +15 -4
- package/dist/esm/index.js +0 -2
- package/dist/esm/parse.js +0 -72
- package/dist/esm/transform.js +0 -116
- package/dist/esm/types.js +0 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.js +0 -2
- package/dist/parse.d.ts +0 -2
- package/dist/parse.js +0 -72
- package/dist/transform.d.ts +0 -2
- package/dist/transform.js +0 -114
- package/dist/types/index.d.ts +0 -3
- package/dist/types/parse.d.ts +0 -2
- package/dist/types/transform.d.ts +0 -2
- package/dist/types/types.d.ts +0 -36
- package/dist/types.d.ts +0 -36
- package/dist/types.js +0 -1
package/README.md
CHANGED
|
@@ -9,12 +9,11 @@ A YAML-based DSL for representing design system component anatomies — platform
|
|
|
9
9
|
node:
|
|
10
10
|
uri: global.component.button
|
|
11
11
|
styles:
|
|
12
|
-
layout.type:
|
|
13
|
-
layout.direction: horizontal
|
|
12
|
+
layout.type: inline-flex
|
|
14
13
|
layout.align: center
|
|
15
|
-
spacing.internal: spacing
|
|
16
|
-
appearance.background: color
|
|
17
|
-
appearance.radius: radius
|
|
14
|
+
spacing.internal.inline.start: spacing.inset.action.inline
|
|
15
|
+
appearance.background: [modifier.color.foreground.primary, color.foreground.primary]
|
|
16
|
+
appearance.radius: dimension.radius.medium
|
|
18
17
|
edges:
|
|
19
18
|
- node:
|
|
20
19
|
uri: global.subcomponent.button-icon
|
|
@@ -28,7 +27,201 @@ node:
|
|
|
28
27
|
slotName: default
|
|
29
28
|
```
|
|
30
29
|
|
|
31
|
-
Style values are **
|
|
30
|
+
Style values are **token symbols**, written in the symbol's own dotted
|
|
31
|
+
spelling: `dimension.radius.medium`, `color.foreground.primary`. A list is
|
|
32
|
+
the **fallback order** — a one-to-one transcription of the implementation's
|
|
33
|
+
own `var()` chain, so `[modifier.color.foreground.primary,
|
|
34
|
+
color.foreground.primary]` says "read the primary-foreground channel, and
|
|
35
|
+
fall back to the primary foreground token", which is what the reference CSS
|
|
36
|
+
does. A **channel** (`modifier.*`, `surface.*`) is a symbol like any other:
|
|
37
|
+
the anatomy consumes it by name where the implementation reads the channel,
|
|
38
|
+
and the semantic token by name where it reads the token — nothing is
|
|
39
|
+
inferred. Primitives like `inline-flex` and `center` are used for layout
|
|
40
|
+
semantics that do not vary across themes, and one may end a list where the
|
|
41
|
+
implementation's chain ends in a literal
|
|
42
|
+
(`[modifier.color.icon, modifier.color.text, currentColor]`).
|
|
43
|
+
|
|
44
|
+
Keys come from a **closed roster** — `definitions/style-keys.yaml`, from
|
|
45
|
+
which `definitions/registry.ttl` is generated. The roster is measured, not
|
|
46
|
+
designed: its 111 keys are the right-hand column of a table whose left-hand
|
|
47
|
+
column was every CSS property bound on a component selector across 89
|
|
48
|
+
published component stylesheets, so a key exists because an implementation
|
|
49
|
+
binds the property. That measurement lives in `canonical/design-system`,
|
|
50
|
+
where the reference stylesheets are an input; this package reads no CSS and
|
|
51
|
+
holds the roster, with its provenance stated in both files. The
|
|
52
|
+
slash-delimited path and the trailing `?` marker of earlier versions are
|
|
53
|
+
retired — see `docs/api-reference.md` §5, §11 and §12.
|
|
54
|
+
|
|
55
|
+
## Projections
|
|
56
|
+
|
|
57
|
+
Projections bind the anatomy tree directly to graph data, after the Relay
|
|
58
|
+
fragment-colocation pattern: the tree carries its data requirements the way a
|
|
59
|
+
lens component carries its fragment.
|
|
60
|
+
|
|
61
|
+
```yaml
|
|
62
|
+
---
|
|
63
|
+
node:
|
|
64
|
+
uri: global.component.entity-card
|
|
65
|
+
projection:
|
|
66
|
+
on: Component # type condition — the tree is a view over one Component
|
|
67
|
+
edges:
|
|
68
|
+
- node:
|
|
69
|
+
uri: global.subcomponent.entity-card-header
|
|
70
|
+
projection:
|
|
71
|
+
field: _meta.title # this node renders the entity's title
|
|
72
|
+
relation:
|
|
73
|
+
cardinality: "1"
|
|
74
|
+
slotName: header
|
|
75
|
+
- node:
|
|
76
|
+
uri: global.component.chip
|
|
77
|
+
projection:
|
|
78
|
+
field: _meta.title # relative to the traversed Tag
|
|
79
|
+
relation:
|
|
80
|
+
cardinality: "0..*"
|
|
81
|
+
slotName: tags
|
|
82
|
+
projection:
|
|
83
|
+
field: documentationStages # traversal populating the slot
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Semantics:
|
|
87
|
+
|
|
88
|
+
- The **root node's** `projection.on` establishes the data context — the anatomy
|
|
89
|
+
is a parameterized view over one entity of that GraphQL type, like
|
|
90
|
+
`fragment EntityCard on Component`. Children inherit the context.
|
|
91
|
+
- `projection.field` on a **relation** is a traversal: the slot is populated
|
|
92
|
+
from that field of the current context. The DSL names the field only —
|
|
93
|
+
never `edges.node`. Anatomy cardinality ↔ graph multiplicity is the Relay
|
|
94
|
+
pattern taken one level further.
|
|
95
|
+
- **Cardinality decomposes against the schema.** The upper bound claims
|
|
96
|
+
multiplicity: `..1` maps to an object or scalar field, `..*` to a
|
|
97
|
+
connection or list. The lower bound claims nullability: `0..` tolerates
|
|
98
|
+
`null`, `1..` requires the provider to always have the value. So
|
|
99
|
+
`field: _meta.title` may sit under `cardinality: "1"` (title is total in
|
|
100
|
+
the contract), but a nullable field like `summary` must sit under `0..1`.
|
|
101
|
+
`1..*` asserts a non-empty list — deliberately stronger than GraphQL can
|
|
102
|
+
express, and checkable only at runtime.
|
|
103
|
+
- **Mechanism-blindness.** Whether a plural field is a Relay connection
|
|
104
|
+
(`subcomponents`) or a plain list (`properties`) is a provider mechanism,
|
|
105
|
+
not an anatomy fact. Consumers discover the shape from the SDL and unwrap
|
|
106
|
+
`edges { node }` when needed; an anatomy survives a provider promoting a
|
|
107
|
+
list to a connection unchanged.
|
|
108
|
+
- `projection.on` on a **child node** narrows the traversed entity's type — the
|
|
109
|
+
analog of an inline fragment. Switch cases carrying different `on` values
|
|
110
|
+
mirror an interface resolved through inline fragments.
|
|
111
|
+
- `projection.field` on a **node** means the node renders that field's value,
|
|
112
|
+
as a dot-delimited path relative to the enclosing context (`_meta.title`).
|
|
113
|
+
- A node projection needs at least one of `on` / `field`; a relation projection
|
|
114
|
+
requires `field` and admits no type condition (narrowing belongs on the
|
|
115
|
+
child node).
|
|
116
|
+
|
|
117
|
+
Field names are anchored to the provider schema the docsite runs against
|
|
118
|
+
(compiled with `prefixing: "none"`); the committed SDL is the naming authority.
|
|
119
|
+
|
|
120
|
+
A fully projected anatomy **derives a GraphQL fragment** mechanically — the
|
|
121
|
+
example above reads as:
|
|
122
|
+
|
|
123
|
+
```graphql
|
|
124
|
+
fragment EntityCardAnatomy on Component {
|
|
125
|
+
_meta { title } # header node
|
|
126
|
+
documentationStages { # tags relation (connection per SDL)
|
|
127
|
+
edges { node { _meta { title } } } # each chip
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
The derivation rules (type conditions, dot-path expansion, connection
|
|
133
|
+
unwrapping, inline fragments) are specified in the API reference §3.10
|
|
134
|
+
*Derived fragment*; a worked gallery of every projection form is in §13, and
|
|
135
|
+
`examples/yaml/entity-card.anatomy.yaml` is the golden example.
|
|
136
|
+
|
|
137
|
+
## Props (pinned values)
|
|
138
|
+
|
|
139
|
+
A named node can **pin** props of the component it references — fixing a prop
|
|
140
|
+
value at one tree position:
|
|
141
|
+
|
|
142
|
+
```yaml
|
|
143
|
+
node:
|
|
144
|
+
uri: global.component.icon
|
|
145
|
+
props:
|
|
146
|
+
icon: chevron-down
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
This is how icons become idiomatic with zero icon-specific machinery. The
|
|
150
|
+
design system models the icon as a component whose glyph is a required prop
|
|
151
|
+
(`ds:global.component.icon` › `ds:hasProperty [ ds:name "icon" ]`), so icon
|
|
152
|
+
usage in anatomies splits into exactly two cases:
|
|
153
|
+
|
|
154
|
+
- **Consumer-filled icon slot** — an icon-component edge with a slot and no
|
|
155
|
+
pin. The consumer chooses the glyph; the anatomy correctly says nothing.
|
|
156
|
+
- **Component-intrinsic icon** — the accordion chevron, the modal close ×, a
|
|
157
|
+
status glyph: the component's own spec fixes the glyph, and the anatomy
|
|
158
|
+
pins it.
|
|
159
|
+
|
|
160
|
+
Semantics:
|
|
161
|
+
|
|
162
|
+
- Pins live on **named nodes only** — anonymous nodes have no prop surface.
|
|
163
|
+
This is enforced in the types, the parser, and SHACL.
|
|
164
|
+
- The DSL **never defines a prop surface** (names, types, optionality live in
|
|
165
|
+
the design system ontology); it only asserts values. Whether a pinned prop
|
|
166
|
+
exists on the component, and whether a value is admissible (e.g. a glyph
|
|
167
|
+
name in the icon set), are consumer-side checks against the DS graph — the
|
|
168
|
+
same posture as projection checking against the provider SDL.
|
|
169
|
+
- Values are scalars, coerced to strings — no symbols and no fallback lists: a pin is a value, not a style.
|
|
170
|
+
Pins are *values with meaning*, not styles: a theme may reskin what
|
|
171
|
+
`chevron-down` looks like (asset layer), but never remap which glyph an
|
|
172
|
+
anatomy means.
|
|
173
|
+
- A **data-driven** value is a projection (`projection: { field: … }`), and a
|
|
174
|
+
**state-driven** one is a `switch` — pinned props are static by design.
|
|
175
|
+
|
|
176
|
+
In TTL, pins reify like styles do: `hasProp [ a :Prop ; propName "…" ;
|
|
177
|
+
propValue "…" ]`. See `examples/yaml/status-header.anatomy.yaml` for
|
|
178
|
+
intrinsic icons, a status-glyph switch, and an unpinned consumer slot in one
|
|
179
|
+
anatomy.
|
|
180
|
+
|
|
181
|
+
## Interaction states
|
|
182
|
+
|
|
183
|
+
Interaction states re-value style channels — they never add structure. A
|
|
184
|
+
style key takes an `@state` suffix scoping its value to a state; the unmarked
|
|
185
|
+
key is the default state:
|
|
186
|
+
|
|
187
|
+
```yaml
|
|
188
|
+
styles:
|
|
189
|
+
interaction.cursor: pointer
|
|
190
|
+
interaction.cursor@disabled: not-allowed
|
|
191
|
+
appearance.background: [modifier.color.foreground.primary, color.foreground.primary]
|
|
192
|
+
appearance.background@hover: color.foreground.primary.hover
|
|
193
|
+
appearance.background@disabled: color.foreground.primary.disabled
|
|
194
|
+
# A slot may read a channel only in one state.
|
|
195
|
+
appearance.outline.color@focus: [modifier.color.focusRing, color.focusRing, currentColor]
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
The state vocabulary is **closed and registry-governed**: `hover`, `active`,
|
|
199
|
+
`focus`, `disabled`, `selected`. Naming follows the industry consensus where
|
|
200
|
+
systems diverge — `active` subsumes Material's *pressed* and Spectrum's
|
|
201
|
+
*down* (and matches Canonical's own token tree); `focus` maps to CSS
|
|
202
|
+
`:focus-visible` (Spectrum's *key-focus*). `@default` is invalid — absence is
|
|
203
|
+
the default. Candidate additions (`checked`, `visited`, `dragged`, `pending`,
|
|
204
|
+
`error`, `read-only`) go through the registry, never by loosening the schema.
|
|
205
|
+
|
|
206
|
+
The boundaries that keep "state machines out of scope" true:
|
|
207
|
+
|
|
208
|
+
- **States hold style values only.** A state that changes the tree is not a
|
|
209
|
+
state — it is a `switch on: internal` case (async-button's
|
|
210
|
+
idle/loading/success/error). The DSL declares appearance *per* state, never
|
|
211
|
+
transitions, triggers, or logic.
|
|
212
|
+
- **Gate vs appearance**: `props: { disabled: true }` (or the consumer) puts
|
|
213
|
+
a node in the disabled state; `…@disabled` styles say how it looks there.
|
|
214
|
+
- A state-scoped value that differs from its base state's is a **lint, not a
|
|
215
|
+
constraint**: it is reported with the ranks at which they differ and never
|
|
216
|
+
rejected, because the reference does it — Button's `:disabled` reads
|
|
217
|
+
`color.text.disabled`, a different symbol from its resting `color.text`.
|
|
218
|
+
- The grammar reserves repeatable markers for compound states
|
|
219
|
+
(`@selected@hover`, canonical order: value/control state before user-action
|
|
220
|
+
state); v1 permits a single `@`.
|
|
221
|
+
|
|
222
|
+
In TTL, the Style tuple gains one optional dimension:
|
|
223
|
+
`[ a :Style ; :styleKey "appearance.background" ; :styleState "hover" ; :styleValue "…" ]`.
|
|
224
|
+
See `examples/yaml/stateful-button.anatomy.yaml`.
|
|
32
225
|
|
|
33
226
|
## Install
|
|
34
227
|
|
|
@@ -50,19 +243,19 @@ const ttl = anatomyToTTL(spec);
|
|
|
50
243
|
The button example above produces:
|
|
51
244
|
|
|
52
245
|
```turtle
|
|
53
|
-
@prefix : <
|
|
246
|
+
@prefix : <https://anatomy.canonical.com/> .
|
|
247
|
+
@prefix dt: <https://dt.canonical.com/> .
|
|
54
248
|
|
|
55
249
|
[] a :Specification ;
|
|
56
250
|
:rootNode [
|
|
57
251
|
a :NamedNode ;
|
|
58
252
|
:uri "global.component.button" ;
|
|
59
253
|
:hasStyle
|
|
60
|
-
[ :styleKey "layout.type" ; :styleValue "
|
|
61
|
-
[ :styleKey "layout.
|
|
62
|
-
[ :styleKey "
|
|
63
|
-
[ :styleKey "
|
|
64
|
-
[ :styleKey "appearance.
|
|
65
|
-
[ :styleKey "appearance.radius" ; :styleValue "radius/button" ] ;
|
|
254
|
+
[ a :Style ; :styleKey "layout.type" ; :styleValue "inline-flex" ] ,
|
|
255
|
+
[ a :Style ; :styleKey "layout.align" ; :styleValue "center" ] ,
|
|
256
|
+
[ a :Style ; :styleKey "spacing.internal.inline.start" ; :styleValue "spacing.inset.action.inline" ; :consumes ( dt:spacing.inset.action.inline ) ] ,
|
|
257
|
+
[ a :Style ; :styleKey "appearance.background" ; :styleValue "[modifier.color.foreground.primary, color.foreground.primary]" ; :consumes ( dt:modifier.color.foreground.primary dt:color.foreground.primary ) ] ,
|
|
258
|
+
[ a :Style ; :styleKey "appearance.radius" ; :styleValue "dimension.radius.medium" ; :consumes ( dt:dimension.radius.medium ) ] ;
|
|
66
259
|
:hasEdge [
|
|
67
260
|
a :Edge ;
|
|
68
261
|
:edgeTarget [
|
|
@@ -111,31 +304,53 @@ All types mirror the [OWL ontology](definitions/ontology.ttl) exactly:
|
|
|
111
304
|
| `Node` | `NamedNode \| AnonymousNode` (discriminated on `type`) |
|
|
112
305
|
| `Edge` | Reified parent→child relationship |
|
|
113
306
|
| `Relation` | Cardinality and optional slot name |
|
|
114
|
-
| `Style` | Reified key-value tuple |
|
|
307
|
+
| `Style` | Reified key-value tuple, with an optional interaction `state` dimension |
|
|
115
308
|
| `Switch` | Polymorphic position (discriminator: `props \| internal \| override`) |
|
|
116
309
|
| `SwitchCase` | One alternative within a switch |
|
|
310
|
+
| `Projection` | Fragment-style graph binding on a node (`on` type condition and/or `field` path) |
|
|
311
|
+
| `RelationProjection` | Traversal populating a slot (`field` required) |
|
|
312
|
+
| `Prop` | Pinned prop value on a named node (reified name-value tuple) |
|
|
117
313
|
|
|
118
314
|
## Repository Structure
|
|
119
315
|
|
|
120
316
|
```
|
|
121
|
-
definitions/ Turtle ontology (OWL) + SHACL shapes
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
317
|
+
definitions/ Turtle ontology (OWL) + SHACL shapes, the style-key roster and
|
|
318
|
+
the registry generated from it, and the lift fixture — all of
|
|
319
|
+
it public API, since exports lists it
|
|
320
|
+
docs/ API reference (WD404 + WD404.1 + WD404.2 + WD404.3)
|
|
321
|
+
examples/ Example anatomy files (YAML + Turtle pairs), the corpus the
|
|
322
|
+
round-trip and SHACL tests read
|
|
323
|
+
src/ TypeScript types, parser, value grammar, transform and the
|
|
324
|
+
generators
|
|
126
325
|
```
|
|
127
326
|
|
|
128
327
|
## Scope
|
|
129
328
|
|
|
130
|
-
The Anatomy DSL describes **structure
|
|
329
|
+
The Anatomy DSL describes **structure**, **graph-data bindings** (projections
|
|
330
|
+
— what data each position renders), **pinned prop values** (props — fixed
|
|
331
|
+
component configuration at a position), and **state-scoped styles**
|
|
332
|
+
(interaction states — how channels re-value per state). It does not handle:
|
|
131
333
|
|
|
132
|
-
- **Prop
|
|
133
|
-
|
|
334
|
+
- **Prop surface definition** — which props a component accepts, their types
|
|
335
|
+
and optionality live in the design system ontology; the DSL only pins values
|
|
336
|
+
- **State machines** — transitions, triggers, and interaction logic; the DSL
|
|
337
|
+
declares appearance per state only, and structural state variation is the
|
|
338
|
+
switch construct's job
|
|
134
339
|
- **Modifier descriptions** — only design token references are supported, not semantic modifier definitions
|
|
135
340
|
|
|
136
341
|
## Design Notes
|
|
137
342
|
|
|
138
|
-
Styles are modelled as reified key-value tuples
|
|
343
|
+
Styles are modelled as reified key-value tuples
|
|
344
|
+
(`hasStyle [ a :Style ; :styleKey "…" ; :styleValue "…" ]`), which stays
|
|
345
|
+
lossless whatever the key roster becomes. Since 0.4.0 a tuple whose key takes
|
|
346
|
+
a token also carries `:consumes`, an ordered `rdf:List` of the `dt:` symbols
|
|
347
|
+
it reads: `:styleValue` is the authored spelling kept verbatim as evidence —
|
|
348
|
+
it is where a terminal literal lives, since a literal is not a symbol — and
|
|
349
|
+
`:consumes` is the form a query can walk. The key vocabulary is no longer
|
|
350
|
+
open: `anatomy:styleKey`'s `sh:in` is projected from the registry, so a key
|
|
351
|
+
outside the roster is a SHACL violation rather than a silent addition.
|
|
352
|
+
|
|
353
|
+
Projections and pinned props reuse the same reification idiom (`hasProjection [ projectionType "…" ; projectionField "…" ]`, `hasProp [ propName "…" ; propValue "…" ]`). Projections attach to both nodes and relations — the reified `Relation` is precisely what makes slot-level traversal annotations possible without changing the `Edge` class; pins attach to named nodes only.
|
|
139
354
|
|
|
140
355
|
## Specification Status
|
|
141
356
|
|
|
@@ -143,3 +358,5 @@ Styles are modelled as reified key-value tuples (`hasStyle [ styleKey "…" ; st
|
|
|
143
358
|
|---------|--------------------------|----------------|
|
|
144
359
|
| [WD404](https://docs.google.com/document/d/1eFr-SNsAZyidnZzpWp1Jeegiat_SSM7mOW_G8p3nXo8/edit?tab=t.pndvuecem8cf) | Anatomy DSL | Approved |
|
|
145
360
|
| [WD404.1](https://docs.google.com/document/d/1eFr-SNsAZyidnZzpWp1Jeegiat_SSM7mOW_G8p3nXo8/edit?tab=t.pndvuecem8cf) | Anatomy DSL — Addendum 1 | Pending Review |
|
|
361
|
+
| WD404.2 | Anatomy DSL — Projections | Draft (this repository) |
|
|
362
|
+
| WD404.3 | Anatomy DSL — Prop pinning | Draft (this repository) |
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": [
|
|
3
|
+
"The committed lift fixture (ADR J §4.1, §8.2). Both TypeScript repositories assert against it, so anatomy-dsl and design-system lift identically and cannot drift apart without a failing test.",
|
|
4
|
+
"`values` is the value lift this package implements: `liftSymbols(value)` over what the YAML parser holds for a style value — a scalar or a sequence of scalars — returning the symbols in fallback order. A primitive is not a symbol: it resolves against nothing by design, so it is never lifted.",
|
|
5
|
+
"`rejections` is the retired notation, with the rule each breaks. Every one throws an AnatomyValueError naming the value and the rule.",
|
|
6
|
+
"`names` is the CSS-name lift of §5.1 step 5d, which design-system implements because it needs S4 to run: a custom-property name to the dotted symbol it lifts to, or null where it names no symbol in any stratum and is kept as consumed with a register row (AT.09). It is DATA here, not an implementation — this package ships it so the two repositories agree on the expected pairs."
|
|
7
|
+
],
|
|
8
|
+
"values": [
|
|
9
|
+
{ "case": "a symbol", "value": "color.text", "symbols": ["color.text"] },
|
|
10
|
+
{
|
|
11
|
+
"case": "a camelCase segment is the symbol's own spelling, not renamed",
|
|
12
|
+
"value": "color.focusRing",
|
|
13
|
+
"symbols": ["color.focusRing"]
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"case": "a numeric segment",
|
|
17
|
+
"value": "dimension.100",
|
|
18
|
+
"symbols": ["dimension.100"]
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"case": "a deep camelCase symbol",
|
|
22
|
+
"value": "typography.weight.semiBold",
|
|
23
|
+
"symbols": ["typography.weight.semiBold"]
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"case": "a channel symbol, consumed by name where the implementation reads the channel",
|
|
27
|
+
"value": "modifier.color.text",
|
|
28
|
+
"symbols": ["modifier.color.text"]
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"case": "the surface channel spelling",
|
|
32
|
+
"value": "surface.color.background",
|
|
33
|
+
"symbols": ["surface.color.background"]
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"case": "the programme's central binding: a channel then the semantic token",
|
|
37
|
+
"value": ["modifier.color.text", "color.text"],
|
|
38
|
+
"symbols": ["modifier.color.text", "color.text"]
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"case": "three ranks, in the reference's own order",
|
|
42
|
+
"value": ["modifier.color.border", "color.border.highlighted", "color.border"],
|
|
43
|
+
"symbols": [
|
|
44
|
+
"modifier.color.border",
|
|
45
|
+
"color.border.highlighted",
|
|
46
|
+
"color.border"
|
|
47
|
+
]
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"case": "a terminal literal is kept in the value and lifted from none of it",
|
|
51
|
+
"value": ["modifier.color.icon", "modifier.color.text", "currentColor"],
|
|
52
|
+
"symbols": ["modifier.color.icon", "modifier.color.text"]
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"case": "a dimension tail",
|
|
56
|
+
"value": ["dimension.stroke.thickness.large", "2px"],
|
|
57
|
+
"symbols": ["dimension.stroke.thickness.large"]
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"case": "a bare keyword, for a key the registry says takes a primitive",
|
|
61
|
+
"value": "flow",
|
|
62
|
+
"symbols": []
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"case": "a hyphenated CSS keyword, which the reference uses throughout",
|
|
66
|
+
"value": "not-allowed",
|
|
67
|
+
"symbols": []
|
|
68
|
+
},
|
|
69
|
+
{ "case": "a number", "value": "1.6", "symbols": [] },
|
|
70
|
+
{ "case": "a percentage", "value": "0%", "symbols": [] },
|
|
71
|
+
{ "case": "a colour", "value": "#ccc", "symbols": [] },
|
|
72
|
+
{
|
|
73
|
+
"case": "a literal holding a slash between spaces: the slash ban is on symbol spellings, never on literals",
|
|
74
|
+
"value": "1 / -1",
|
|
75
|
+
"symbols": []
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"case": "a literal a bare YAML would read as an alias",
|
|
79
|
+
"value": "*",
|
|
80
|
+
"symbols": []
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"case": "an undeclared name is still a symbol by grammar: whether it resolves is the register's question, not the parser's",
|
|
84
|
+
"value": ["modifier.surface", "color.foreground.primary"],
|
|
85
|
+
"symbols": ["modifier.surface", "color.foreground.primary"]
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"case": "a computed state variable, kept as consumed under the hover.X spelling",
|
|
89
|
+
"value": ["hover.color.foreground.secondary", "color.foreground.secondary.hover"],
|
|
90
|
+
"symbols": [
|
|
91
|
+
"hover.color.foreground.secondary",
|
|
92
|
+
"color.foreground.secondary.hover"
|
|
93
|
+
]
|
|
94
|
+
}
|
|
95
|
+
],
|
|
96
|
+
"rejections": [
|
|
97
|
+
{ "case": "a slash path", "value": "spacing/medium", "rule": "slashPath" },
|
|
98
|
+
{
|
|
99
|
+
"case": "a slash path with a camelCase-bearing segment",
|
|
100
|
+
"value": "color/focus-ring",
|
|
101
|
+
"rule": "slashPath"
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"case": "a slash path inside a sequence",
|
|
105
|
+
"value": ["modifier/color/foreground", "color/foreground/primary"],
|
|
106
|
+
"rule": "slashPath"
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"case": "the retired optional marker on a slash path",
|
|
110
|
+
"value": "color/surface/button?",
|
|
111
|
+
"rule": "slashPath"
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"case": "the retired optional marker on a dotted symbol",
|
|
115
|
+
"value": "color.surface.button?",
|
|
116
|
+
"rule": "marker"
|
|
117
|
+
},
|
|
118
|
+
{ "case": "the reserved root segment", "value": "$root", "rule": "root" },
|
|
119
|
+
{
|
|
120
|
+
"case": "the reserved root segment inside a dotted name",
|
|
121
|
+
"value": "color.$root.text",
|
|
122
|
+
"rule": "root"
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"case": "a bare root segment",
|
|
126
|
+
"value": "color.root.text",
|
|
127
|
+
"rule": "root"
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"case": "a primitive before the end: the sequence is the fallback order and a literal is what the chain ends in",
|
|
131
|
+
"value": ["2px", "dimension.stroke.thickness.large"],
|
|
132
|
+
"rule": "primitiveNotLast"
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"case": "a primitive in the middle",
|
|
136
|
+
"value": ["modifier.color.text", "currentColor", "color.text"],
|
|
137
|
+
"rule": "primitiveNotLast"
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"case": "a one-element sequence: a fallback order needs two",
|
|
141
|
+
"value": ["color.text"],
|
|
142
|
+
"rule": "singleton"
|
|
143
|
+
},
|
|
144
|
+
{ "case": "an empty sequence", "value": [], "rule": "empty" },
|
|
145
|
+
{ "case": "an empty scalar", "value": "", "rule": "empty" }
|
|
146
|
+
],
|
|
147
|
+
"names": [
|
|
148
|
+
{
|
|
149
|
+
"case": "a plain semantic token",
|
|
150
|
+
"variable": "--color-text",
|
|
151
|
+
"symbol": "color.text"
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"case": "camelCase is restored from the kebab spelling S4 emits",
|
|
155
|
+
"variable": "--color-focus-ring",
|
|
156
|
+
"symbol": "color.focusRing"
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
"case": "the drift spelling S4 also emits for the same symbol",
|
|
160
|
+
"variable": "--color-focusRing",
|
|
161
|
+
"symbol": "color.focusRing"
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"case": "a modifier channel",
|
|
165
|
+
"variable": "--modifier-color-text",
|
|
166
|
+
"symbol": "modifier.color.text"
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
"case": "a surface channel",
|
|
170
|
+
"variable": "--surface-color-background",
|
|
171
|
+
"symbol": "surface.color.background"
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
"case": "a channel with no base symbol: kept as consumed, register row X7",
|
|
175
|
+
"variable": "--modifier-surface",
|
|
176
|
+
"symbol": "modifier.surface",
|
|
177
|
+
"resolves": false
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
"case": "a state variant of a channel: register row X5",
|
|
181
|
+
"variable": "--modifier-surface-hover",
|
|
182
|
+
"symbol": "modifier.surface.hover",
|
|
183
|
+
"resolves": false
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
"case": "a computed state variable: the double dash lifts to a dot, register row X15",
|
|
187
|
+
"variable": "--hover--color-foreground-secondary",
|
|
188
|
+
"symbol": "hover.color.foreground.secondary",
|
|
189
|
+
"resolves": false
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
"case": "a component-local variable is followed through its :root definition and replaced by what it consumes",
|
|
193
|
+
"variable": "--button-color-text",
|
|
194
|
+
"symbol": "color.text",
|
|
195
|
+
"via": "--button-color-text: var(--color-text)"
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
"case": "a component-local variable whose definition is itself a chain",
|
|
199
|
+
"variable": "--button-color-background",
|
|
200
|
+
"symbol": "modifier.color.foreground.primary",
|
|
201
|
+
"via": "--button-color-background: var(--modifier-color-foreground-primary, var(--color-foreground-primary))"
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
"case": "an undeclared name: kept as consumed with a comment, register row X1",
|
|
205
|
+
"variable": "--motion-duration-fast",
|
|
206
|
+
"symbol": "motion.duration.fast",
|
|
207
|
+
"resolves": false
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
"case": "a composite typography slot no stratum declares",
|
|
211
|
+
"variable": "--typography-text-secondary-font-size",
|
|
212
|
+
"symbol": "typography.text.secondary.fontSize",
|
|
213
|
+
"resolves": false
|
|
214
|
+
}
|
|
215
|
+
]
|
|
216
|
+
}
|