Named values
Every string-literal union that code branches on has a const object of the same name. Code compares against the object, as in head === Arrowhead.triangle, never against a raw string. ADR 0006 (docs/decisions/0006-named-values-in-comparisons.md) records the rule, and the draw/no-literal-comparison lint rule enforces it. Data is still written with plain strings, as in { kind: "arrow", ... }.
| Object | Values | Type it names |
|---|---|---|
Arrowhead |
none, arrow, triangle, dot, bar |
Arrowhead |
DrawableKind |
rectangle, ellipse, diamond, line, freehand, text, arrow |
Drawable["kind"] |
HandleKind |
vertex, terminal, bend |
Handle["kind"] |
FillKind |
none, hachure, crossHatch ("cross-hatch"), solid |
Fill["kind"] |
StrokeStyle |
solid, dashed, dotted |
StrokeStyle |
TextSizing |
auto, fixedWidth ("fixed-width") |
TextSizing |
A hyphenated value gets a camelCase key: FillKind.crossHatch is "cross-hatch".
Keeping objects and types in step
For a standalone union, the object is the source and the type is derived from it, as in type Arrowhead = (typeof Arrowhead)[keyof typeof Arrowhead].
For a discriminant inside a bigger union, such as Drawable["kind"], the union stays the source. The object is annotated with that type, as in export const DrawableKind: NamedValues<DrawableKind> = { ... }. NamedValues<U> maps each member of U to a camelCase key whose value is that member, so the compiler rejects an object that misses a member, adds one, or misspells one.
type NamedValues<U extends string> = { readonly [K in U as Camel<K>]: K };Other packages and apps use NamedValues for their own unions. @draw/sketch uses it for PathCommandKind and DrawOpKind.