@canonical/anatomy-dsl 0.5.0 → 0.6.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 +10 -6
- package/definitions/ontology.ttl +8 -4
- package/definitions/registry.ttl +21 -19
- package/definitions/shapes.ttl +9 -2
- package/definitions/style-keys.yaml +33 -21
- package/dist/esm/index.js +4 -0
- package/dist/esm/parse.js +138 -0
- package/dist/esm/registry.generated.js +465 -0
- package/dist/esm/transform.js +181 -0
- package/dist/esm/types.js +1 -0
- package/dist/esm/value.js +154 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/parse.d.ts +17 -0
- package/dist/types/registry.generated.d.ts +22 -0
- package/dist/types/transform.d.ts +2 -0
- package/dist/types/types.d.ts +89 -0
- package/dist/types/value.d.ts +100 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -196,12 +196,16 @@ styles:
|
|
|
196
196
|
```
|
|
197
197
|
|
|
198
198
|
The state vocabulary is **closed and registry-governed**: `hover`, `active`,
|
|
199
|
-
`focus`, `disabled`, `selected
|
|
200
|
-
systems diverge — `active`
|
|
201
|
-
*down* (and matches Canonical's
|
|
202
|
-
`:focus-visible` (Spectrum's
|
|
203
|
-
|
|
204
|
-
`
|
|
199
|
+
`focus`, `disabled`, `selected`, `expanded`, `indeterminate`, `invalid`.
|
|
200
|
+
Naming follows the industry consensus where systems diverge — `active`
|
|
201
|
+
subsumes Material's *pressed* and Spectrum's *down* (and matches Canonical's
|
|
202
|
+
own token tree); `focus` maps to CSS `:focus-visible` (Spectrum's
|
|
203
|
+
*key-focus*); `expanded` is a disclosure's open state (`[open]`,
|
|
204
|
+
`aria-expanded`); `indeterminate` is a mixed checkbox or progress bar, on
|
|
205
|
+
neither setting; `invalid` is a failed validation (`:invalid`,
|
|
206
|
+
`aria-invalid`). `@default` is not admitted — absence is the default.
|
|
207
|
+
Candidate additions (`checked`, `visited`, `dragged`, `pending`, `error`,
|
|
208
|
+
`read-only`) go through the registry, never by loosening the schema.
|
|
205
209
|
|
|
206
210
|
The boundaries that keep "state machines out of scope" true:
|
|
207
211
|
|
package/definitions/ontology.ttl
CHANGED
|
@@ -340,13 +340,17 @@ anatomy:styleState a owl:DatatypeProperty ;
|
|
|
340
340
|
rdfs:range xsd:string ;
|
|
341
341
|
skos:definition """Interaction state this style value applies in. Absent
|
|
342
342
|
means the default state. Closed vocabulary (hover, active, focus,
|
|
343
|
-
disabled, selected), governed by
|
|
344
|
-
'pressed' and Spectrum's 'down';
|
|
345
|
-
(Spectrum's 'key-focus')
|
|
343
|
+
disabled, selected, expanded, indeterminate, invalid), governed by
|
|
344
|
+
registry — 'active' subsumes Material's 'pressed' and Spectrum's 'down';
|
|
345
|
+
'focus' maps to CSS :focus-visible (Spectrum's 'key-focus'); 'expanded'
|
|
346
|
+
is a disclosure's open state ([open], aria-expanded); 'indeterminate' is
|
|
347
|
+
a mixed checkbox or progress bar; 'invalid' is a failed validation
|
|
348
|
+
(:invalid, aria-invalid). States re-value style channels only: they never
|
|
346
349
|
add structure (that is the Switch construct) and never define transitions
|
|
347
350
|
or interaction logic (state machines stay out of scope). Authored as an
|
|
348
351
|
@state suffix on the style key.""" ;
|
|
349
|
-
skos:example "hover" , "active" , "focus" , "disabled" , "selected"
|
|
352
|
+
skos:example "hover" , "active" , "focus" , "disabled" , "selected" ,
|
|
353
|
+
"expanded" , "indeterminate" , "invalid" .
|
|
350
354
|
|
|
351
355
|
anatomy:projectionType a owl:DatatypeProperty ;
|
|
352
356
|
rdfs:label "projection type" ;
|
package/definitions/registry.ttl
CHANGED
|
@@ -14,7 +14,9 @@
|
|
|
14
14
|
# shapes state that disjunction.
|
|
15
15
|
# anatomy:tokenNamespace the dotted prefixes whose symbols the key admits,
|
|
16
16
|
# multi-valued: N., modifier.N. and surface.N., so a
|
|
17
|
-
# channel binding is not a namespace violation.
|
|
17
|
+
# channel binding is not a namespace violation. A key
|
|
18
|
+
# may admit more than one base namespace, and then it
|
|
19
|
+
# carries the three spellings of each.
|
|
18
20
|
#
|
|
19
21
|
# PROVENANCE. The roster is not designed: these 111 keys were measured from
|
|
20
22
|
# the 89 published component stylesheets of
|
|
@@ -460,109 +462,109 @@ anatomy:key.spacing.external.block.end
|
|
|
460
462
|
a anatomy:StyleKey ;
|
|
461
463
|
rdfs:label "spacing.external.block.end" ;
|
|
462
464
|
anatomy:valueKind "either" ;
|
|
463
|
-
anatomy:tokenNamespace "spacing." , "modifier.spacing." , "surface.spacing." .
|
|
465
|
+
anatomy:tokenNamespace "spacing." , "modifier.spacing." , "surface.spacing." , "dimension." , "modifier.dimension." , "surface.dimension." .
|
|
464
466
|
|
|
465
467
|
anatomy:key.spacing.external.block.start
|
|
466
468
|
a anatomy:StyleKey ;
|
|
467
469
|
rdfs:label "spacing.external.block.start" ;
|
|
468
470
|
anatomy:valueKind "either" ;
|
|
469
|
-
anatomy:tokenNamespace "spacing." , "modifier.spacing." , "surface.spacing." .
|
|
471
|
+
anatomy:tokenNamespace "spacing." , "modifier.spacing." , "surface.spacing." , "dimension." , "modifier.dimension." , "surface.dimension." .
|
|
470
472
|
|
|
471
473
|
anatomy:key.spacing.external.bottom
|
|
472
474
|
a anatomy:StyleKey ;
|
|
473
475
|
rdfs:label "spacing.external.bottom" ;
|
|
474
476
|
anatomy:valueKind "either" ;
|
|
475
|
-
anatomy:tokenNamespace "spacing." , "modifier.spacing." , "surface.spacing." .
|
|
477
|
+
anatomy:tokenNamespace "spacing." , "modifier.spacing." , "surface.spacing." , "dimension." , "modifier.dimension." , "surface.dimension." .
|
|
476
478
|
|
|
477
479
|
anatomy:key.spacing.external.inline.end
|
|
478
480
|
a anatomy:StyleKey ;
|
|
479
481
|
rdfs:label "spacing.external.inline.end" ;
|
|
480
482
|
anatomy:valueKind "either" ;
|
|
481
|
-
anatomy:tokenNamespace "spacing." , "modifier.spacing." , "surface.spacing." .
|
|
483
|
+
anatomy:tokenNamespace "spacing." , "modifier.spacing." , "surface.spacing." , "dimension." , "modifier.dimension." , "surface.dimension." .
|
|
482
484
|
|
|
483
485
|
anatomy:key.spacing.external.inline.start
|
|
484
486
|
a anatomy:StyleKey ;
|
|
485
487
|
rdfs:label "spacing.external.inline.start" ;
|
|
486
488
|
anatomy:valueKind "either" ;
|
|
487
|
-
anatomy:tokenNamespace "spacing." , "modifier.spacing." , "surface.spacing." .
|
|
489
|
+
anatomy:tokenNamespace "spacing." , "modifier.spacing." , "surface.spacing." , "dimension." , "modifier.dimension." , "surface.dimension." .
|
|
488
490
|
|
|
489
491
|
anatomy:key.spacing.external.left
|
|
490
492
|
a anatomy:StyleKey ;
|
|
491
493
|
rdfs:label "spacing.external.left" ;
|
|
492
494
|
anatomy:valueKind "either" ;
|
|
493
|
-
anatomy:tokenNamespace "spacing." , "modifier.spacing." , "surface.spacing." .
|
|
495
|
+
anatomy:tokenNamespace "spacing." , "modifier.spacing." , "surface.spacing." , "dimension." , "modifier.dimension." , "surface.dimension." .
|
|
494
496
|
|
|
495
497
|
anatomy:key.spacing.external.right
|
|
496
498
|
a anatomy:StyleKey ;
|
|
497
499
|
rdfs:label "spacing.external.right" ;
|
|
498
500
|
anatomy:valueKind "either" ;
|
|
499
|
-
anatomy:tokenNamespace "spacing." , "modifier.spacing." , "surface.spacing." .
|
|
501
|
+
anatomy:tokenNamespace "spacing." , "modifier.spacing." , "surface.spacing." , "dimension." , "modifier.dimension." , "surface.dimension." .
|
|
500
502
|
|
|
501
503
|
anatomy:key.spacing.external.top
|
|
502
504
|
a anatomy:StyleKey ;
|
|
503
505
|
rdfs:label "spacing.external.top" ;
|
|
504
506
|
anatomy:valueKind "either" ;
|
|
505
|
-
anatomy:tokenNamespace "spacing." , "modifier.spacing." , "surface.spacing." .
|
|
507
|
+
anatomy:tokenNamespace "spacing." , "modifier.spacing." , "surface.spacing." , "dimension." , "modifier.dimension." , "surface.dimension." .
|
|
506
508
|
|
|
507
509
|
anatomy:key.spacing.gap
|
|
508
510
|
a anatomy:StyleKey ;
|
|
509
511
|
rdfs:label "spacing.gap" ;
|
|
510
512
|
anatomy:valueKind "either" ;
|
|
511
|
-
anatomy:tokenNamespace "spacing." , "modifier.spacing." , "surface.spacing." .
|
|
513
|
+
anatomy:tokenNamespace "spacing." , "modifier.spacing." , "surface.spacing." , "dimension." , "modifier.dimension." , "surface.dimension." .
|
|
512
514
|
|
|
513
515
|
anatomy:key.spacing.gap.block
|
|
514
516
|
a anatomy:StyleKey ;
|
|
515
517
|
rdfs:label "spacing.gap.block" ;
|
|
516
518
|
anatomy:valueKind "token" ;
|
|
517
|
-
anatomy:tokenNamespace "spacing." , "modifier.spacing." , "surface.spacing." .
|
|
519
|
+
anatomy:tokenNamespace "spacing." , "modifier.spacing." , "surface.spacing." , "dimension." , "modifier.dimension." , "surface.dimension." .
|
|
518
520
|
|
|
519
521
|
anatomy:key.spacing.gap.inline
|
|
520
522
|
a anatomy:StyleKey ;
|
|
521
523
|
rdfs:label "spacing.gap.inline" ;
|
|
522
524
|
anatomy:valueKind "token" ;
|
|
523
|
-
anatomy:tokenNamespace "spacing." , "modifier.spacing." , "surface.spacing." .
|
|
525
|
+
anatomy:tokenNamespace "spacing." , "modifier.spacing." , "surface.spacing." , "dimension." , "modifier.dimension." , "surface.dimension." .
|
|
524
526
|
|
|
525
527
|
anatomy:key.spacing.internal.block.end
|
|
526
528
|
a anatomy:StyleKey ;
|
|
527
529
|
rdfs:label "spacing.internal.block.end" ;
|
|
528
530
|
anatomy:valueKind "either" ;
|
|
529
|
-
anatomy:tokenNamespace "spacing." , "modifier.spacing." , "surface.spacing." .
|
|
531
|
+
anatomy:tokenNamespace "spacing." , "modifier.spacing." , "surface.spacing." , "dimension." , "modifier.dimension." , "surface.dimension." .
|
|
530
532
|
|
|
531
533
|
anatomy:key.spacing.internal.block.start
|
|
532
534
|
a anatomy:StyleKey ;
|
|
533
535
|
rdfs:label "spacing.internal.block.start" ;
|
|
534
536
|
anatomy:valueKind "either" ;
|
|
535
|
-
anatomy:tokenNamespace "spacing." , "modifier.spacing." , "surface.spacing." .
|
|
537
|
+
anatomy:tokenNamespace "spacing." , "modifier.spacing." , "surface.spacing." , "dimension." , "modifier.dimension." , "surface.dimension." .
|
|
536
538
|
|
|
537
539
|
anatomy:key.spacing.internal.bottom
|
|
538
540
|
a anatomy:StyleKey ;
|
|
539
541
|
rdfs:label "spacing.internal.bottom" ;
|
|
540
542
|
anatomy:valueKind "either" ;
|
|
541
|
-
anatomy:tokenNamespace "spacing." , "modifier.spacing." , "surface.spacing." .
|
|
543
|
+
anatomy:tokenNamespace "spacing." , "modifier.spacing." , "surface.spacing." , "dimension." , "modifier.dimension." , "surface.dimension." .
|
|
542
544
|
|
|
543
545
|
anatomy:key.spacing.internal.inline.end
|
|
544
546
|
a anatomy:StyleKey ;
|
|
545
547
|
rdfs:label "spacing.internal.inline.end" ;
|
|
546
548
|
anatomy:valueKind "either" ;
|
|
547
|
-
anatomy:tokenNamespace "spacing." , "modifier.spacing." , "surface.spacing." .
|
|
549
|
+
anatomy:tokenNamespace "spacing." , "modifier.spacing." , "surface.spacing." , "dimension." , "modifier.dimension." , "surface.dimension." .
|
|
548
550
|
|
|
549
551
|
anatomy:key.spacing.internal.inline.start
|
|
550
552
|
a anatomy:StyleKey ;
|
|
551
553
|
rdfs:label "spacing.internal.inline.start" ;
|
|
552
554
|
anatomy:valueKind "either" ;
|
|
553
|
-
anatomy:tokenNamespace "spacing." , "modifier.spacing." , "surface.spacing." .
|
|
555
|
+
anatomy:tokenNamespace "spacing." , "modifier.spacing." , "surface.spacing." , "dimension." , "modifier.dimension." , "surface.dimension." .
|
|
554
556
|
|
|
555
557
|
anatomy:key.spacing.internal.left
|
|
556
558
|
a anatomy:StyleKey ;
|
|
557
559
|
rdfs:label "spacing.internal.left" ;
|
|
558
560
|
anatomy:valueKind "either" ;
|
|
559
|
-
anatomy:tokenNamespace "spacing." , "modifier.spacing." , "surface.spacing." .
|
|
561
|
+
anatomy:tokenNamespace "spacing." , "modifier.spacing." , "surface.spacing." , "dimension." , "modifier.dimension." , "surface.dimension." .
|
|
560
562
|
|
|
561
563
|
anatomy:key.spacing.internal.top
|
|
562
564
|
a anatomy:StyleKey ;
|
|
563
565
|
rdfs:label "spacing.internal.top" ;
|
|
564
566
|
anatomy:valueKind "either" ;
|
|
565
|
-
anatomy:tokenNamespace "spacing." , "modifier.spacing." , "surface.spacing." .
|
|
567
|
+
anatomy:tokenNamespace "spacing." , "modifier.spacing." , "surface.spacing." , "dimension." , "modifier.dimension." , "surface.dimension." .
|
|
566
568
|
|
|
567
569
|
anatomy:key.typography.align
|
|
568
570
|
a anatomy:StyleKey ;
|
package/definitions/shapes.ttl
CHANGED
|
@@ -511,6 +511,13 @@ anatomy:StyleShape a sh:NodeShape ;
|
|
|
511
511
|
# candidates), never through loosening this list ad hoc. There is no
|
|
512
512
|
# "default" state: an absent styleState IS the default.
|
|
513
513
|
#
|
|
514
|
+
# expanded, indeterminate and invalid entered that way, and the line they
|
|
515
|
+
# sit on is worth stating: a state is admitted where the implementation
|
|
516
|
+
# RE-VALUES a channel in it, and refused where it changes what exists. A
|
|
517
|
+
# disclosure header with a different background while open is the first;
|
|
518
|
+
# the panel that exists only while open is the second, and stays the
|
|
519
|
+
# Switch construct's.
|
|
520
|
+
#
|
|
514
521
|
# A state value that differs from its base state's is a LINT, not a
|
|
515
522
|
# constraint (AT.11): the reference does it — Button's :disabled reads a
|
|
516
523
|
# different symbol — so it is reported with its ranks and never rejected,
|
|
@@ -519,8 +526,8 @@ anatomy:StyleShape a sh:NodeShape ;
|
|
|
519
526
|
sh:path anatomy:styleState ;
|
|
520
527
|
sh:maxCount 1 ;
|
|
521
528
|
sh:datatype xsd:string ;
|
|
522
|
-
sh:in ( "hover" "active" "focus" "disabled" "selected" ) ;
|
|
523
|
-
sh:message "styleState must be one of: hover, active, focus, disabled, selected (absent = default state)." ;
|
|
529
|
+
sh:in ( "hover" "active" "focus" "disabled" "selected" "expanded" "indeterminate" "invalid" ) ;
|
|
530
|
+
sh:message "styleState must be one of: hover, active, focus, disabled, selected, expanded, indeterminate, invalid (absent = default state)." ;
|
|
524
531
|
] ;
|
|
525
532
|
|
|
526
533
|
# BEGIN GENERATED valueKind sh:or — bun run generate:shapes
|
|
@@ -49,14 +49,22 @@
|
|
|
49
49
|
# needs: `N.`, `modifier.N.` and `surface.N.`, because a
|
|
50
50
|
# channel of a symbol in `N.` is what the anatomy consumes
|
|
51
51
|
# where the implementation reads the channel.
|
|
52
|
+
# A key that admits symbols from more than one namespace
|
|
53
|
+
# states them as a LIST — `[spacing., dimension.]` — and each
|
|
54
|
+
# one is projected into its own three spellings, grouped by
|
|
55
|
+
# namespace and in the order written here. One namespace and
|
|
56
|
+
# a one-element list mean the same thing; the scalar form is
|
|
57
|
+
# the ordinary one, and the list says a key really does draw
|
|
58
|
+
# on two populations.
|
|
52
59
|
# why: required on a `primitive` key whose reference values read a
|
|
53
60
|
# `var()` anyway — a component-local count, an asset — and
|
|
54
61
|
# emitted as the key's `rdfs:comment`, so the exception is
|
|
55
62
|
# stated and not silent. Forbidden on a key that takes a token.
|
|
56
63
|
#
|
|
57
|
-
# `spacing.*` keys admit `spacing.`
|
|
58
|
-
# reference binds `dimension.*` there throughout, and
|
|
59
|
-
# the
|
|
64
|
+
# `spacing.*` keys admit `spacing.` and, for now, `dimension.` too: the
|
|
65
|
+
# reference binds `dimension.*` there throughout, and until semantic spacing
|
|
66
|
+
# lands the roster admits both rather than sending every real binding to the
|
|
67
|
+
# register. The second namespace is temporary and is marked as such below.
|
|
60
68
|
#
|
|
61
69
|
# `motion.` is declared as a namespace though no stratum publishes one yet,
|
|
62
70
|
# so a `motion.duration.fast` binding is admitted and its absence is a
|
|
@@ -291,60 +299,64 @@ keys:
|
|
|
291
299
|
valueKind: either
|
|
292
300
|
namespace: dimension.
|
|
293
301
|
|
|
302
|
+
# dimension. is admitted temporarily — the implementations read
|
|
303
|
+
# `--dimension-*` directly for padding and gaps, and the semantic spacing
|
|
304
|
+
# namespace is still thin (twelve symbols). Remove `dimension.` here once
|
|
305
|
+
# semantic spacing lands (ADR J ledger, row G1).
|
|
294
306
|
spacing.external.block.end:
|
|
295
307
|
valueKind: either
|
|
296
|
-
namespace: spacing.
|
|
308
|
+
namespace: [spacing., dimension.]
|
|
297
309
|
spacing.external.block.start:
|
|
298
310
|
valueKind: either
|
|
299
|
-
namespace: spacing.
|
|
311
|
+
namespace: [spacing., dimension.]
|
|
300
312
|
spacing.external.bottom:
|
|
301
313
|
valueKind: either
|
|
302
|
-
namespace: spacing.
|
|
314
|
+
namespace: [spacing., dimension.]
|
|
303
315
|
spacing.external.inline.end:
|
|
304
316
|
valueKind: either
|
|
305
|
-
namespace: spacing.
|
|
317
|
+
namespace: [spacing., dimension.]
|
|
306
318
|
spacing.external.inline.start:
|
|
307
319
|
valueKind: either
|
|
308
|
-
namespace: spacing.
|
|
320
|
+
namespace: [spacing., dimension.]
|
|
309
321
|
spacing.external.left:
|
|
310
322
|
valueKind: either
|
|
311
|
-
namespace: spacing.
|
|
323
|
+
namespace: [spacing., dimension.]
|
|
312
324
|
spacing.external.right:
|
|
313
325
|
valueKind: either
|
|
314
|
-
namespace: spacing.
|
|
326
|
+
namespace: [spacing., dimension.]
|
|
315
327
|
spacing.external.top:
|
|
316
328
|
valueKind: either
|
|
317
|
-
namespace: spacing.
|
|
329
|
+
namespace: [spacing., dimension.]
|
|
318
330
|
spacing.gap:
|
|
319
331
|
valueKind: either
|
|
320
|
-
namespace: spacing.
|
|
332
|
+
namespace: [spacing., dimension.]
|
|
321
333
|
spacing.gap.block:
|
|
322
334
|
valueKind: token
|
|
323
|
-
namespace: spacing.
|
|
335
|
+
namespace: [spacing., dimension.]
|
|
324
336
|
spacing.gap.inline:
|
|
325
337
|
valueKind: token
|
|
326
|
-
namespace: spacing.
|
|
338
|
+
namespace: [spacing., dimension.]
|
|
327
339
|
spacing.internal.block.end:
|
|
328
340
|
valueKind: either
|
|
329
|
-
namespace: spacing.
|
|
341
|
+
namespace: [spacing., dimension.]
|
|
330
342
|
spacing.internal.block.start:
|
|
331
343
|
valueKind: either
|
|
332
|
-
namespace: spacing.
|
|
344
|
+
namespace: [spacing., dimension.]
|
|
333
345
|
spacing.internal.bottom:
|
|
334
346
|
valueKind: either
|
|
335
|
-
namespace: spacing.
|
|
347
|
+
namespace: [spacing., dimension.]
|
|
336
348
|
spacing.internal.inline.end:
|
|
337
349
|
valueKind: either
|
|
338
|
-
namespace: spacing.
|
|
350
|
+
namespace: [spacing., dimension.]
|
|
339
351
|
spacing.internal.inline.start:
|
|
340
352
|
valueKind: either
|
|
341
|
-
namespace: spacing.
|
|
353
|
+
namespace: [spacing., dimension.]
|
|
342
354
|
spacing.internal.left:
|
|
343
355
|
valueKind: either
|
|
344
|
-
namespace: spacing.
|
|
356
|
+
namespace: [spacing., dimension.]
|
|
345
357
|
spacing.internal.top:
|
|
346
358
|
valueKind: either
|
|
347
|
-
namespace: spacing.
|
|
359
|
+
namespace: [spacing., dimension.]
|
|
348
360
|
|
|
349
361
|
typography.align:
|
|
350
362
|
valueKind: primitive
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { parseAnatomyYAML } from "./parse.js";
|
|
2
|
+
export { PLACEHOLDER_SEGMENTS, STYLE_KEYS, takesToken, } from "./registry.generated.js";
|
|
3
|
+
export { anatomyToTTL } from "./transform.js";
|
|
4
|
+
export { AnatomyValueError, authoredSpelling, classifyElement, liftSymbols, parseStyleValue, RULES, } from "./value.js";
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { parseDocument } from "yaml";
|
|
2
|
+
import { authoredSpelling, liftSymbols } from "./value.js";
|
|
3
|
+
/**
|
|
4
|
+
* The one lift every consumer shares. It is implemented in `./value.js`,
|
|
5
|
+
* beside the grammar it belongs to, and re-exported here because the parser
|
|
6
|
+
* is where a reader looks for it.
|
|
7
|
+
*/
|
|
8
|
+
export { liftSymbols } from "./value.js";
|
|
9
|
+
/**
|
|
10
|
+
* Parse an anatomy document.
|
|
11
|
+
*
|
|
12
|
+
* Give it the YAML text — which is what design-system holds, one
|
|
13
|
+
* `ds:anatomyDsl` literal per block — and it is parsed here, with the
|
|
14
|
+
* `yaml` package's Document API; that is why `yaml` is a runtime dependency
|
|
15
|
+
* and not a devDependency. An already-parsed value is still accepted, for a
|
|
16
|
+
* caller that has one.
|
|
17
|
+
*/
|
|
18
|
+
export function parseAnatomyYAML(raw) {
|
|
19
|
+
const doc = (typeof raw === "string" ? parseDocument(raw).toJS() : raw);
|
|
20
|
+
if (doc === null || typeof doc !== "object" || doc.node === undefined) {
|
|
21
|
+
throw new Error("An anatomy document is a mapping with one `node` key");
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
root: toNamedNode(doc.node),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function toNamedNode(raw) {
|
|
28
|
+
return {
|
|
29
|
+
type: "named",
|
|
30
|
+
uri: raw.uri,
|
|
31
|
+
...(raw.projection ? { projection: toProjection(raw.projection) } : {}),
|
|
32
|
+
...(raw.props ? { props: toProps(raw.props) } : {}),
|
|
33
|
+
...(raw.styles ? { styles: toStyles(raw.styles) } : {}),
|
|
34
|
+
...(raw.edges ? { edges: raw.edges.map(toEdge) } : {}),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function toNode(raw) {
|
|
38
|
+
if ("uri" in raw && raw.uri) {
|
|
39
|
+
return toNamedNode(raw);
|
|
40
|
+
}
|
|
41
|
+
if ("props" in raw) {
|
|
42
|
+
throw new Error("Props are only allowed on named nodes");
|
|
43
|
+
}
|
|
44
|
+
const anon = raw;
|
|
45
|
+
return {
|
|
46
|
+
type: "anonymous",
|
|
47
|
+
role: anon.role,
|
|
48
|
+
...(anon.projection ? { projection: toProjection(anon.projection) } : {}),
|
|
49
|
+
...(anon.styles ? { styles: toStyles(anon.styles) } : {}),
|
|
50
|
+
...(anon.edges ? { edges: anon.edges.map(toEdge) } : {}),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function toProjection(raw) {
|
|
54
|
+
if (raw.on === undefined && raw.field === undefined) {
|
|
55
|
+
throw new Error("Projection must have at least one of on, field");
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
...(raw.on !== undefined ? { on: raw.on } : {}),
|
|
59
|
+
...(raw.field !== undefined ? { field: raw.field } : {}),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function toStyles(raw) {
|
|
63
|
+
return Object.entries(raw).map(([rawKey, value]) => {
|
|
64
|
+
const [key, state, ...rest] = rawKey.split("@");
|
|
65
|
+
if (rest.length > 0) {
|
|
66
|
+
throw new Error(`Compound states are not yet supported: ${rawKey}`);
|
|
67
|
+
}
|
|
68
|
+
if (state === "") {
|
|
69
|
+
throw new Error(`Style key has an empty state: ${rawKey}`);
|
|
70
|
+
}
|
|
71
|
+
if (state === "default") {
|
|
72
|
+
throw new Error(`The unmarked key is the default state — drop "@default": ${rawKey}`);
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
key: key,
|
|
76
|
+
// The authored spelling is kept verbatim as evidence; the symbols are
|
|
77
|
+
// lifted from it by the one lift every consumer shares (§4.1).
|
|
78
|
+
value: authoredSpelling(value),
|
|
79
|
+
symbols: liftSymbols(value, key),
|
|
80
|
+
...(state !== undefined ? { state } : {}),
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
function toProps(raw) {
|
|
85
|
+
return Object.entries(raw).map(([name, value]) => ({
|
|
86
|
+
name,
|
|
87
|
+
value: String(value),
|
|
88
|
+
}));
|
|
89
|
+
}
|
|
90
|
+
function toEdge(raw) {
|
|
91
|
+
let target;
|
|
92
|
+
if (raw.switch) {
|
|
93
|
+
target = toSwitch(raw.switch);
|
|
94
|
+
}
|
|
95
|
+
else if (raw.node) {
|
|
96
|
+
target = toNode(raw.node);
|
|
97
|
+
}
|
|
98
|
+
else if (raw.uri) {
|
|
99
|
+
target = { type: "named", uri: raw.uri };
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
throw new Error("Edge must have node, uri, or switch");
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
target,
|
|
106
|
+
relation: toRelation(raw.relation),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function toSwitch(raw) {
|
|
110
|
+
return {
|
|
111
|
+
discriminator: raw.on,
|
|
112
|
+
cases: raw.cases.map(toSwitchCase),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function toSwitchCase(raw) {
|
|
116
|
+
if (raw.uri !== undefined) {
|
|
117
|
+
const node = { type: "named", uri: raw.uri };
|
|
118
|
+
return { value: raw.uri, node };
|
|
119
|
+
}
|
|
120
|
+
const node = toNode(raw.node);
|
|
121
|
+
const value = node.type === "named" ? node.uri : node.role;
|
|
122
|
+
return { value, node };
|
|
123
|
+
}
|
|
124
|
+
function toRelation(raw) {
|
|
125
|
+
return {
|
|
126
|
+
cardinality: raw.cardinality,
|
|
127
|
+
...(raw.slotName ? { slotName: raw.slotName } : {}),
|
|
128
|
+
...(raw.projection
|
|
129
|
+
? { projection: toRelationProjection(raw.projection) }
|
|
130
|
+
: {}),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
function toRelationProjection(raw) {
|
|
134
|
+
if (!raw.field) {
|
|
135
|
+
throw new Error("Relation projection requires a field");
|
|
136
|
+
}
|
|
137
|
+
return { field: raw.field };
|
|
138
|
+
}
|