@velarscript/web 0.10.1
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/LICENSE +201 -0
- package/README.md +89 -0
- package/dist/analyzer.d.ts +302 -0
- package/dist/analyzer.d.ts.map +1 -0
- package/dist/analyzer.js +2931 -0
- package/dist/analyzer.js.map +1 -0
- package/dist/ast.d.ts +220 -0
- package/dist/ast.d.ts.map +1 -0
- package/dist/ast.js +102 -0
- package/dist/ast.js.map +1 -0
- package/dist/browser-test.d.ts +29 -0
- package/dist/browser-test.d.ts.map +1 -0
- package/dist/browser-test.js +33 -0
- package/dist/browser-test.js.map +1 -0
- package/dist/compiler.d.ts +9 -0
- package/dist/compiler.d.ts.map +1 -0
- package/dist/compiler.js +715 -0
- package/dist/compiler.js.map +1 -0
- package/dist/css-tokens.d.ts +46 -0
- package/dist/css-tokens.d.ts.map +1 -0
- package/dist/css-tokens.js +363 -0
- package/dist/css-tokens.js.map +1 -0
- package/dist/editor.d.ts +3 -0
- package/dist/editor.d.ts.map +1 -0
- package/dist/editor.js +147 -0
- package/dist/editor.js.map +1 -0
- package/dist/elements.d.ts +11 -0
- package/dist/elements.d.ts.map +1 -0
- package/dist/elements.js +24 -0
- package/dist/elements.js.map +1 -0
- package/dist/emitter.d.ts +79 -0
- package/dist/emitter.d.ts.map +1 -0
- package/dist/emitter.js +2699 -0
- package/dist/emitter.js.map +1 -0
- package/dist/host.d.ts +7 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +224 -0
- package/dist/host.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/inspection.d.ts +3 -0
- package/dist/inspection.d.ts.map +1 -0
- package/dist/inspection.js +82 -0
- package/dist/inspection.js.map +1 -0
- package/dist/keyframes.d.ts +16 -0
- package/dist/keyframes.d.ts.map +1 -0
- package/dist/keyframes.js +29 -0
- package/dist/keyframes.js.map +1 -0
- package/dist/lexer.d.ts +85 -0
- package/dist/lexer.d.ts.map +1 -0
- package/dist/lexer.js +422 -0
- package/dist/lexer.js.map +1 -0
- package/dist/look-static.d.ts +31 -0
- package/dist/look-static.d.ts.map +1 -0
- package/dist/look-static.js +266 -0
- package/dist/look-static.js.map +1 -0
- package/dist/look.d.ts +134 -0
- package/dist/look.d.ts.map +1 -0
- package/dist/look.js +627 -0
- package/dist/look.js.map +1 -0
- package/dist/parser.d.ts +71 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +976 -0
- package/dist/parser.js.map +1 -0
- package/dist/project-config.d.ts +33 -0
- package/dist/project-config.d.ts.map +1 -0
- package/dist/project-config.js +199 -0
- package/dist/project-config.js.map +1 -0
- package/dist/runtime-foundation.d.ts +7 -0
- package/dist/runtime-foundation.d.ts.map +1 -0
- package/dist/runtime-foundation.js +1245 -0
- package/dist/runtime-foundation.js.map +1 -0
- package/dist/runtime.d.ts +7 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +3444 -0
- package/dist/runtime.js.map +1 -0
- package/dist/semantic.d.ts +3 -0
- package/dist/semantic.d.ts.map +1 -0
- package/dist/semantic.js +150 -0
- package/dist/semantic.js.map +1 -0
- package/dist/types.d.ts +56 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +178 -0
- package/dist/types.js.map +1 -0
- package/dist/websocket-runtime.d.ts +2 -0
- package/dist/websocket-runtime.d.ts.map +1 -0
- package/dist/websocket-runtime.js +30 -0
- package/dist/websocket-runtime.js.map +1 -0
- package/dist/worker-runtime.d.ts +2 -0
- package/dist/worker-runtime.d.ts.map +1 -0
- package/dist/worker-runtime.js +72 -0
- package/dist/worker-runtime.js.map +1 -0
- package/package.json +52 -0
package/dist/look.js
ADDED
|
@@ -0,0 +1,627 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One source of truth for every suffix owned by the Web visual language.
|
|
3
|
+
* The lexer, analyzer, public-interface inference, and editor all consume this
|
|
4
|
+
* table so a unit cannot silently mean different things at different stages.
|
|
5
|
+
*/
|
|
6
|
+
export const LOOK_UNIT_TYPES = new Map([
|
|
7
|
+
...["px", "rem", "em", "vw", "vh", "vmin", "vmax"].map((unit) => [unit, "Length"]),
|
|
8
|
+
["%", "Percentage"],
|
|
9
|
+
["fr", "TrackFraction"],
|
|
10
|
+
...["ms", "s"].map((unit) => [unit, "Duration"]),
|
|
11
|
+
...["deg", "turn"].map((unit) => [unit, "Angle"]),
|
|
12
|
+
]);
|
|
13
|
+
export const LOOK_MEDIA_LENGTH_UNITS = new Set(["px", "rem", "em"]);
|
|
14
|
+
/**
|
|
15
|
+
* The closed set of media-condition subjects a Look condition may name. Every
|
|
16
|
+
* subject lowers to a CSS media query, so the whole set stays live for the
|
|
17
|
+
* lifetime of the page. The names are reserved bindings in a Web module: a
|
|
18
|
+
* user binding of the same name would otherwise be shadowed by this table
|
|
19
|
+
* inside Look conditions only (LOK-D4).
|
|
20
|
+
*/
|
|
21
|
+
export const LOOK_MEDIA_SUBJECTS = new Map([
|
|
22
|
+
["viewport", new Set(["width", "height"])],
|
|
23
|
+
["scheme", new Set(["dark", "light"])],
|
|
24
|
+
["motion", new Set(["reduced"])],
|
|
25
|
+
]);
|
|
26
|
+
/** Media features a Web developer may reach for that Look deliberately omits. */
|
|
27
|
+
export const LOOK_ABSENT_MEDIA_SUBJECTS = new Set(["container", "print", "orientation", "screen", "device", "pointer", "resolution", "display", "contrast", "colors"]);
|
|
28
|
+
/**
|
|
29
|
+
* Properties whose CSS grammar takes a bare number. Every other numeric Look
|
|
30
|
+
* property is a length, so a unitless number would reach CSS as a dead
|
|
31
|
+
* declaration; the property tables reject it and the diagnostic teaches the
|
|
32
|
+
* unit (LOK-D3).
|
|
33
|
+
*/
|
|
34
|
+
export const LOOK_UNITLESS_PROPERTIES = new Set([
|
|
35
|
+
"lineHeight", "opacity", "zIndex", "fontWeight", "flex", "flexGrow", "flexShrink", "order", "scale", "aspectRatio",
|
|
36
|
+
]);
|
|
37
|
+
/** Builders that compose lengths: a unitless argument other than 0 is dead CSS. */
|
|
38
|
+
export const LOOK_LENGTH_BUILDERS = new Set(["spacing", "tracks", "minmax", "min", "max", "clamp", "border", "shadow"]);
|
|
39
|
+
/**
|
|
40
|
+
* The numeric domains the velar/look builders enforce at run time. A literal
|
|
41
|
+
* argument is checked in the same terms while the module compiles, so an
|
|
42
|
+
* out-of-range colour never reaches a blank page (LOK-U8).
|
|
43
|
+
*/
|
|
44
|
+
export const LOOK_BUILDER_NUMERIC_RANGES = new Map([
|
|
45
|
+
["rgb", [["RGB channel 1", 0, 255], ["RGB channel 2", 0, 255], ["RGB channel 3", 0, 255]]],
|
|
46
|
+
["rgba", [["RGB channel 1", 0, 255], ["RGB channel 2", 0, 255], ["RGB channel 3", 0, 255], ["RGB alpha", 0, 1]]],
|
|
47
|
+
["hsl", [null, ["HSL saturation", 0, 100], ["HSL lightness", 0, 100]]],
|
|
48
|
+
["alpha", [null, ["Color opacity", 0, 1]]],
|
|
49
|
+
["lighten", [null, ["Color amount", 0, 1]]],
|
|
50
|
+
["darken", [null, ["Color amount", 0, 1]]],
|
|
51
|
+
]);
|
|
52
|
+
/** The border styles the border builder accepts, mirroring its runtime guard. */
|
|
53
|
+
export const LOOK_BORDER_STYLE_NAMES = new Set(["none", "hidden", "dotted", "dashed", "solid", "double", "groove", "ridge", "inset", "outset"]);
|
|
54
|
+
export const LOOK_ARITHMETIC_HINT = "@velarscript/web:look-arithmetic";
|
|
55
|
+
export const LOOK_NUMERIC_TYPE_NAMES = new Set([
|
|
56
|
+
"Length", "Percentage", "LengthPercentage", "TrackFraction", "Duration", "Angle",
|
|
57
|
+
]);
|
|
58
|
+
// D50 rule 92: 'Opacity' never appeared here. No builder produces it and the
|
|
59
|
+
// 'opacity' property's declared type is number, so the name was unreachable —
|
|
60
|
+
// publishing an unreachable name is worse than publishing nothing.
|
|
61
|
+
export const LOOK_PUBLIC_TYPE_NAMES = Object.freeze([
|
|
62
|
+
"Look", "Length", "Percentage", "LengthPercentage", "TrackFraction", "Color", "Duration", "Angle",
|
|
63
|
+
"Border", "Shadow", "Image", "Track", "TrackList", "Transition", "Spacing", "Keyframes", "Animation",
|
|
64
|
+
]);
|
|
65
|
+
export const LOOK_HOOKS = new Set([
|
|
66
|
+
"hover", "focus", "focusVisible", "active", "current", "disabled", "checked", "invalid", "open",
|
|
67
|
+
]);
|
|
68
|
+
export const LOOK_TARGETS = new Set([
|
|
69
|
+
"before", "after", "backdrop", "placeholder", "selection", "marker", "fileSelectorButton",
|
|
70
|
+
]);
|
|
71
|
+
/**
|
|
72
|
+
* Every velar/look builder and the shape of its call. Three consumers read this
|
|
73
|
+
* one table — the published module interface, the named-argument check, and the
|
|
74
|
+
* static lowering that has to put a named argument back at its position when a
|
|
75
|
+
* builder call appears inside a `keyframes:` stop. D57 rule 134: a list two
|
|
76
|
+
* places need is written once and derived from.
|
|
77
|
+
*/
|
|
78
|
+
export const LOOK_BUILDER_SIGNATURES = new Map([
|
|
79
|
+
["color", { parameters: ["value"], required: 1 }],
|
|
80
|
+
["rgb", { parameters: ["red", "green", "blue"], required: 3 }],
|
|
81
|
+
["rgba", { parameters: ["red", "green", "blue", "alpha"], required: 4 }],
|
|
82
|
+
["hsl", { parameters: ["hue", "saturation", "lightness"], required: 3 }],
|
|
83
|
+
["alpha", { parameters: ["color", "opacity"], required: 2 }],
|
|
84
|
+
["lighten", { parameters: ["color", "amount"], required: 2 }],
|
|
85
|
+
["darken", { parameters: ["color", "amount"], required: 2 }],
|
|
86
|
+
["border", { parameters: ["width", "color", "style"], required: 2 }],
|
|
87
|
+
["shadow", { parameters: ["x", "y", "blur", "color", "spread", "inset"], required: 4 }],
|
|
88
|
+
["linearGradient", { parameters: ["angle", "start", "end"], required: 3 }],
|
|
89
|
+
["asset", { parameters: ["path"], required: 1 }],
|
|
90
|
+
["minmax", { parameters: ["minimum", "maximum"], required: 2 }],
|
|
91
|
+
["repeat", { parameters: ["count", "size"], required: 2 }],
|
|
92
|
+
["tracks", { parameters: ["first"], required: 1, rest: true }],
|
|
93
|
+
["transition", { parameters: ["property", "duration", "easing", "delay"], required: 2 }],
|
|
94
|
+
["spacing", { parameters: ["first", "second", "third", "fourth"], required: 1 }],
|
|
95
|
+
["min", { parameters: ["first", "second"], required: 2 }],
|
|
96
|
+
["max", { parameters: ["first", "second"], required: 2 }],
|
|
97
|
+
["clamp", { parameters: ["minimum", "preferred", "maximum"], required: 3 }],
|
|
98
|
+
["animate", { parameters: ["frames", "duration", "easing", "delay", "count", "loop", "direction", "fill"], required: 2 }],
|
|
99
|
+
]);
|
|
100
|
+
export const LOOK_BUILDERS = new Set(LOOK_BUILDER_SIGNATURES.keys());
|
|
101
|
+
export const LOOK_ANIMATION_EASINGS = new Set([
|
|
102
|
+
"linear", "ease", "ease-in", "ease-out", "ease-in-out", "step-start", "step-end",
|
|
103
|
+
]);
|
|
104
|
+
export const LOOK_ANIMATION_DIRECTIONS = new Set(["normal", "reverse", "alternate", "alternate-reverse"]);
|
|
105
|
+
export const LOOK_ANIMATION_FILLS = new Set(["none", "forwards", "backwards", "both"]);
|
|
106
|
+
/** Properties whose CSS values do not participate in interpolated animation. */
|
|
107
|
+
export const LOOK_NON_ANIMATABLE_PROPERTIES = new Set([
|
|
108
|
+
"display", "position", "overflow", "overflowX", "overflowY", "pointerEvents", "cursor", "userSelect",
|
|
109
|
+
"content", "visibility", "resize", "touchAction", "scrollBehavior", "appearance", "isolation", "contain",
|
|
110
|
+
"animation", "transition", "transitionProperty", "transitionDuration", "transitionDelay", "transitionTimingFunction",
|
|
111
|
+
]);
|
|
112
|
+
/** Published Look vocabulary, grouped by the job each property performs. */
|
|
113
|
+
export const LOOK_PROPERTY_GROUPS = Object.freeze([
|
|
114
|
+
{ family: "layout and containment", properties: ["display", "position", "boxSizing", "isolation", "contain", "visibility", "zIndex", "overflow", "overflowX", "overflowY", "resize", "clip", "clipPath", "objectFit", "objectPosition", "aspectRatio"] },
|
|
115
|
+
{ family: "grid", properties: ["gridTemplateColumns", "gridTemplateRows", "gridTemplateAreas", "gridAutoColumns", "gridAutoRows", "gridAutoFlow", "gridColumn", "gridColumnStart", "gridColumnEnd", "gridRow", "gridRowStart", "gridRowEnd", "gridArea"] },
|
|
116
|
+
{ family: "flex and alignment", properties: ["flex", "flexDirection", "flexGrow", "flexShrink", "flexBasis", "flexWrap", "order", "gap", "rowGap", "columnGap", "alignItems", "justifyItems", "justifyContent", "alignContent", "alignSelf", "justifySelf", "placeItems", "placeContent", "placeSelf"] },
|
|
117
|
+
{ family: "size and inset", properties: ["width", "height", "minWidth", "maxWidth", "minHeight", "maxHeight", "inlineSize", "blockSize", "minInlineSize", "maxInlineSize", "minBlockSize", "maxBlockSize", "inset", "top", "right", "bottom", "left", "insetInline", "insetBlock", "insetInlineStart", "insetInlineEnd", "insetBlockStart", "insetBlockEnd"] },
|
|
118
|
+
{ family: "spacing", properties: ["padding", "paddingTop", "paddingRight", "paddingBottom", "paddingLeft", "paddingInline", "paddingBlock", "paddingInlineStart", "paddingInlineEnd", "paddingBlockStart", "paddingBlockEnd", "margin", "marginTop", "marginRight", "marginBottom", "marginLeft", "marginInline", "marginBlock", "marginInlineStart", "marginInlineEnd", "marginBlockStart", "marginBlockEnd"] },
|
|
119
|
+
{ family: "background", properties: ["background", "backgroundColor", "backgroundImage", "backgroundPosition", "backgroundSize", "backgroundRepeat", "backgroundAttachment", "backgroundClip", "backgroundOrigin", "backgroundBlendMode"] },
|
|
120
|
+
{ family: "border and outline", properties: ["border", "borderWidth", "borderStyle", "borderColor", "borderTop", "borderRight", "borderBottom", "borderLeft", "borderTopWidth", "borderRightWidth", "borderBottomWidth", "borderLeftWidth", "borderTopStyle", "borderRightStyle", "borderBottomStyle", "borderLeftStyle", "borderTopColor", "borderRightColor", "borderBottomColor", "borderLeftColor", "borderRadius", "borderTopLeftRadius", "borderTopRightRadius", "borderBottomRightRadius", "borderBottomLeftRadius", "outline", "outlineWidth", "outlineStyle", "outlineColor", "outlineOffset"] },
|
|
121
|
+
{ family: "effects", properties: ["boxShadow", "textShadow", "opacity", "filter", "backdropFilter", "content"] },
|
|
122
|
+
{ family: "typography and international text", properties: ["color", "font", "fontFamily", "fontSize", "fontWeight", "fontStyle", "fontStretch", "fontVariant", "fontKerning", "fontOpticalSizing", "fontFeatureSettings", "fontVariationSettings", "lineHeight", "letterSpacing", "wordSpacing", "textAlign", "textIndent", "textDecoration", "textDecorationColor", "textDecorationLine", "textDecorationStyle", "textDecorationThickness", "textUnderlineOffset", "textUnderlinePosition", "textTransform", "textRendering", "whiteSpace", "textOverflow", "textWrap", "overflowWrap", "wordBreak", "hyphens", "tabSize", "writingMode", "textOrientation", "direction", "unicodeBidi"] },
|
|
123
|
+
{ family: "lists", properties: ["listStyle", "listStyleType", "listStylePosition", "listStyleImage"] },
|
|
124
|
+
{ family: "SVG paint", properties: ["fill", "stroke", "strokeWidth", "strokeLinecap", "strokeLinejoin", "strokeDasharray", "strokeDashoffset"] },
|
|
125
|
+
{ family: "transform and transition", properties: ["translate", "scale", "rotate", "transform", "transformOrigin", "transition", "transitionProperty", "transitionDuration", "transitionDelay", "transitionTimingFunction", "animation"] },
|
|
126
|
+
{ family: "interaction and form theme", properties: ["cursor", "pointerEvents", "userSelect", "touchAction", "appearance", "accentColor", "caretColor", "colorScheme"] },
|
|
127
|
+
{ family: "scroll", properties: ["scrollBehavior", "scrollMargin", "scrollMarginTop", "scrollMarginRight", "scrollMarginBottom", "scrollMarginLeft", "scrollPadding", "scrollPaddingTop", "scrollPaddingRight", "scrollPaddingBottom", "scrollPaddingLeft", "scrollSnapAlign", "scrollSnapStop", "scrollSnapType", "overscrollBehavior", "overscrollBehaviorX", "overscrollBehaviorY", "scrollbarColor", "scrollbarWidth"] },
|
|
128
|
+
]);
|
|
129
|
+
export const LOOK_PROPERTIES = new Set(LOOK_PROPERTY_GROUPS.flatMap((group) => group.properties));
|
|
130
|
+
const propertyKinds = [
|
|
131
|
+
["animation", ["animation"]],
|
|
132
|
+
["angle", ["rotate"]],
|
|
133
|
+
["background", ["background"]],
|
|
134
|
+
["border", ["border", "borderTop", "borderRight", "borderBottom", "borderLeft", "outline"]],
|
|
135
|
+
["color", ["color", "backgroundColor", "borderColor", "borderTopColor", "borderRightColor", "borderBottomColor", "borderLeftColor", "outlineColor", "textDecorationColor", "accentColor", "caretColor", "fill", "stroke"]],
|
|
136
|
+
["duration", ["transitionDuration", "transitionDelay"]],
|
|
137
|
+
["filter", ["filter", "backdropFilter"]],
|
|
138
|
+
["image", ["backgroundImage", "listStyleImage"]],
|
|
139
|
+
["line-height", ["lineHeight"]],
|
|
140
|
+
["metric", ["gap", "rowGap", "columnGap", "width", "height", "minWidth", "maxWidth", "minHeight", "maxHeight", "inlineSize", "blockSize", "minInlineSize", "maxInlineSize", "minBlockSize", "maxBlockSize", "inset", "top", "right", "bottom", "left", "insetInline", "insetBlock", "insetInlineStart", "insetInlineEnd", "insetBlockStart", "insetBlockEnd", "padding", "paddingTop", "paddingRight", "paddingBottom", "paddingLeft", "paddingInline", "paddingBlock", "paddingInlineStart", "paddingInlineEnd", "paddingBlockStart", "paddingBlockEnd", "margin", "marginTop", "marginRight", "marginBottom", "marginLeft", "marginInline", "marginBlock", "marginInlineStart", "marginInlineEnd", "marginBlockStart", "marginBlockEnd", "flexBasis", "borderWidth", "borderTopWidth", "borderRightWidth", "borderBottomWidth", "borderLeftWidth", "borderRadius", "borderTopLeftRadius", "borderTopRightRadius", "borderBottomRightRadius", "borderBottomLeftRadius", "outlineWidth", "outlineOffset", "fontSize", "letterSpacing", "wordSpacing", "textIndent", "textDecorationThickness", "textUnderlineOffset", "strokeWidth", "strokeDashoffset", "scrollMargin", "scrollMarginTop", "scrollMarginRight", "scrollMarginBottom", "scrollMarginLeft", "scrollPadding", "scrollPaddingTop", "scrollPaddingRight", "scrollPaddingBottom", "scrollPaddingLeft", "translate", "transformOrigin", "backgroundPosition", "objectPosition", "backgroundSize"]],
|
|
141
|
+
["number", ["opacity", "zIndex", "flexGrow", "flexShrink", "order", "tabSize"]],
|
|
142
|
+
["number-keyword", ["fontWeight", "aspectRatio", "scale", "flex"]],
|
|
143
|
+
["shadow", ["boxShadow", "textShadow"]],
|
|
144
|
+
["text", ["content", "font", "fontFamily", "fontFeatureSettings", "fontVariationSettings", "gridTemplateAreas", "gridColumn", "gridColumnStart", "gridColumnEnd", "gridRow", "gridRowStart", "gridRowEnd", "gridArea", "clip", "clipPath", "strokeDasharray", "scrollbarColor"]],
|
|
145
|
+
["track", ["gridTemplateColumns", "gridTemplateRows", "gridAutoColumns", "gridAutoRows"]],
|
|
146
|
+
["transform", ["transform"]],
|
|
147
|
+
["transition", ["transition"]],
|
|
148
|
+
["keyword", ["display", "position", "boxSizing", "isolation", "contain", "visibility", "overflow", "overflowX", "overflowY", "resize", "objectFit", "gridAutoFlow", "flexDirection", "flexWrap", "alignItems", "justifyItems", "justifyContent", "alignContent", "alignSelf", "justifySelf", "placeItems", "placeContent", "placeSelf", "backgroundRepeat", "backgroundAttachment", "backgroundClip", "backgroundOrigin", "backgroundBlendMode", "borderStyle", "borderTopStyle", "borderRightStyle", "borderBottomStyle", "borderLeftStyle", "outlineStyle", "fontStyle", "fontStretch", "fontVariant", "fontKerning", "fontOpticalSizing", "textAlign", "textDecoration", "textDecorationLine", "textDecorationStyle", "textUnderlinePosition", "textTransform", "textRendering", "whiteSpace", "textOverflow", "textWrap", "overflowWrap", "wordBreak", "hyphens", "writingMode", "textOrientation", "direction", "unicodeBidi", "listStyle", "listStyleType", "listStylePosition", "strokeLinecap", "strokeLinejoin", "transitionProperty", "transitionTimingFunction", "cursor", "pointerEvents", "userSelect", "touchAction", "appearance", "colorScheme", "scrollBehavior", "scrollSnapAlign", "scrollSnapStop", "scrollSnapType", "overscrollBehavior", "overscrollBehaviorX", "overscrollBehaviorY", "scrollbarWidth"]],
|
|
149
|
+
];
|
|
150
|
+
export const LOOK_PROPERTY_VALUE_KINDS = new Map(propertyKinds.flatMap(([kind, properties]) => properties.map((property) => [property, kind])));
|
|
151
|
+
for (const property of LOOK_PROPERTIES) {
|
|
152
|
+
if (!LOOK_PROPERTY_VALUE_KINDS.has(property))
|
|
153
|
+
throw new Error(`Look property '${property}' has no declared value kind`);
|
|
154
|
+
}
|
|
155
|
+
const cssWideKeywords = ["inherit", "initial", "revert", "revert-layer", "unset"];
|
|
156
|
+
const keywords = (...values) => new Set([...cssWideKeywords, ...values]);
|
|
157
|
+
/**
|
|
158
|
+
* The five keywords every CSS property accepts. They are kept apart from a
|
|
159
|
+
* property's own vocabulary so a diagnostic can lead with the values that
|
|
160
|
+
* belong to the property being written (D67 rule 174).
|
|
161
|
+
*/
|
|
162
|
+
export const LOOK_CSS_WIDE_KEYWORDS = Object.freeze([...cssWideKeywords]);
|
|
163
|
+
const cssWideKeywordSet = new Set(cssWideKeywords);
|
|
164
|
+
/**
|
|
165
|
+
* D60 rule 150: `transitionProperty` published a name no author could reach —
|
|
166
|
+
* its only accepted values were the generic defaults, so no property name was
|
|
167
|
+
* writable. The vocabulary is derived from the Look property table rather than
|
|
168
|
+
* listed by hand (D57 rule 134): every animatable Look property, spelled the
|
|
169
|
+
* way CSS spells it, plus the two aggregate keywords. A property that does not
|
|
170
|
+
* participate in interpolation is excluded for the same reason `keyframes:`
|
|
171
|
+
* rejects it.
|
|
172
|
+
*/
|
|
173
|
+
const transitionPropertyKeywords = keywords("none", "all", ...[...LOOK_PROPERTIES]
|
|
174
|
+
.filter((property) => !LOOK_NON_ANIMATABLE_PROPERTIES.has(property))
|
|
175
|
+
.map(cssPropertyName));
|
|
176
|
+
/**
|
|
177
|
+
* Every `left right` value of a grammar that takes one token from each of two
|
|
178
|
+
* positions — `y mandatory`, `left top`. D65 rule 169: a closed set holds the
|
|
179
|
+
* complete value the author writes, so a multi-token CSS value is written out
|
|
180
|
+
* here rather than turned into a builder or a token grammar of its own.
|
|
181
|
+
*/
|
|
182
|
+
function lookValuePairs(left, right) {
|
|
183
|
+
const second = [...right];
|
|
184
|
+
return [...left].flatMap((first) => second.map((last) => `${first} ${last}`));
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Every value of a CSS `||` combination: each non-empty selection of the tokens
|
|
188
|
+
* in every order the combinator accepts, so `dense row` is the same value as
|
|
189
|
+
* `row dense` to the author and to the browser.
|
|
190
|
+
*/
|
|
191
|
+
function lookValueOrderings(tokens) {
|
|
192
|
+
return tokens.flatMap((token, index) => {
|
|
193
|
+
const rest = [...tokens.slice(0, index), ...tokens.slice(index + 1)];
|
|
194
|
+
return [token, ...lookValueOrderings(rest).map((tail) => `${token} ${tail}`)];
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
/** Every value of a `[ … ]{1,2}` repetition — `scrollSnapAlign: start end`. */
|
|
198
|
+
function lookValueRepetitions(tokens) {
|
|
199
|
+
return [...tokens, ...lookValuePairs(tokens, tokens)];
|
|
200
|
+
}
|
|
201
|
+
// `<position>`'s keyword grid: one placement word, or one from each axis in
|
|
202
|
+
// either order. D67 rule 172: the three properties that take a `<position>` are
|
|
203
|
+
// all `metric`, so the length and percentage forms are the unit half of that
|
|
204
|
+
// kind rather than a value space anyone has to record as excluded.
|
|
205
|
+
const horizontalPositions = ["left", "center", "right"];
|
|
206
|
+
const verticalPositions = ["top", "center", "bottom"];
|
|
207
|
+
const positionKeywords = [
|
|
208
|
+
...horizontalPositions, ...verticalPositions,
|
|
209
|
+
...lookValuePairs(horizontalPositions, verticalPositions),
|
|
210
|
+
...lookValuePairs(verticalPositions, horizontalPositions),
|
|
211
|
+
];
|
|
212
|
+
// The line part of the text-decoration family, written once: `textDecoration`
|
|
213
|
+
// is the shorthand whose only closed part is this one (D57 rule 134).
|
|
214
|
+
const textDecorationLineKeywords = ["none", ...lookValueOrderings(["underline", "overline", "line-through"])];
|
|
215
|
+
// The predefined counter styles of CSS Counter Styles 3 §6, by its own
|
|
216
|
+
// grouping. A `<string>` marker and a custom `@counter-style` name are the open
|
|
217
|
+
// part and are recorded as excluded.
|
|
218
|
+
const listStyleTypeKeywords = [
|
|
219
|
+
"none",
|
|
220
|
+
// §6.1 numeric
|
|
221
|
+
"decimal", "decimal-leading-zero", "arabic-indic", "armenian", "upper-armenian", "lower-armenian", "bengali",
|
|
222
|
+
"cambodian", "khmer", "cjk-decimal", "devanagari", "georgian", "gujarati", "gurmukhi", "hebrew", "kannada", "lao",
|
|
223
|
+
"malayalam", "mongolian", "myanmar", "oriya", "persian", "lower-roman", "upper-roman", "tamil", "telugu", "thai", "tibetan",
|
|
224
|
+
// §6.2 alphabetic
|
|
225
|
+
"lower-alpha", "lower-latin", "upper-alpha", "upper-latin", "lower-greek",
|
|
226
|
+
"hiragana", "hiragana-iroha", "katakana", "katakana-iroha",
|
|
227
|
+
// §6.3 symbolic
|
|
228
|
+
"disc", "circle", "square", "disclosure-open", "disclosure-closed",
|
|
229
|
+
// §6.4 fixed
|
|
230
|
+
"cjk-earthly-branch", "cjk-heavenly-stem",
|
|
231
|
+
// §6.5 complex, and the two aliases CSS 2.1 shipped
|
|
232
|
+
"japanese-informal", "japanese-formal", "korean-hangul-formal", "korean-hanja-informal", "korean-hanja-formal",
|
|
233
|
+
"simp-chinese-informal", "simp-chinese-formal", "trad-chinese-informal", "trad-chinese-formal",
|
|
234
|
+
"cjk-ideographic", "ethiopic-numeric",
|
|
235
|
+
];
|
|
236
|
+
const listStylePositionKeywords = ["inside", "outside"];
|
|
237
|
+
// `color-scheme` names the same two schemes `if scheme.dark:` conditions on, so
|
|
238
|
+
// it reads that table rather than restating it. `only` may lead or trail the
|
|
239
|
+
// scheme list.
|
|
240
|
+
const colorSchemeOrderings = lookValueOrderings([...LOOK_MEDIA_SUBJECTS.get("scheme") ?? []]);
|
|
241
|
+
const scrollSnapAxes = ["x", "y", "block", "inline", "both"];
|
|
242
|
+
const overscrollBehaviors = ["auto", "contain", "none"];
|
|
243
|
+
// CSS Box Alignment 3's shared value groups. Nine Look properties select from
|
|
244
|
+
// them, and each selects a different combination, so the groups are written
|
|
245
|
+
// once and the nine read them (D57 rule 134). The `<overflow-position>` safe
|
|
246
|
+
// and unsafe prefixes are the part no set holds, and every one of the nine
|
|
247
|
+
// records it. D67 rule 173: `self-start`, `self-end` and the baseline
|
|
248
|
+
// positions are real values all nine used to refuse.
|
|
249
|
+
const alignBaselinePositions = ["baseline", "first baseline", "last baseline"];
|
|
250
|
+
const alignSelfPositions = ["center", "start", "end", "self-start", "self-end", "flex-start", "flex-end"];
|
|
251
|
+
const alignContentPositions = ["center", "start", "end", "flex-start", "flex-end"];
|
|
252
|
+
const alignContentDistributions = ["space-between", "space-around", "space-evenly", "stretch"];
|
|
253
|
+
// `touch-action`'s single tokens, including the four one-way pans and the pinch
|
|
254
|
+
// gesture. D67 rule 173: the property published five of these ten, so
|
|
255
|
+
// `pan-left` and `pinch-zoom` -- values a scroll container really takes -- were
|
|
256
|
+
// refused. The `||` combinations of a horizontal pan, a vertical pan and
|
|
257
|
+
// pinch-zoom are the part no set holds and are recorded as excluded.
|
|
258
|
+
const touchActionKeywords = [
|
|
259
|
+
"auto", "none", "pan-x", "pan-left", "pan-right", "pan-y", "pan-up", "pan-down", "pinch-zoom", "manipulation",
|
|
260
|
+
];
|
|
261
|
+
// `background-repeat`'s `<repeat-style>`: the two one-axis shorthands, or one
|
|
262
|
+
// token per axis. D67 rule 173: `backgroundRepeat = "repeat no-repeat"` is the
|
|
263
|
+
// ordinary way to repeat on one axis only, and the set used to hold six of the
|
|
264
|
+
// twenty-two values that spell it.
|
|
265
|
+
const backgroundRepeatKeywords = [
|
|
266
|
+
"repeat-x", "repeat-y", ...lookValueRepetitions(["repeat", "space", "round", "no-repeat"]),
|
|
267
|
+
];
|
|
268
|
+
export const LOOK_PROPERTY_KEYWORDS = new Map([
|
|
269
|
+
// D67 rule 173: `list-item` is a real display type with no other spelling, so
|
|
270
|
+
// it joins the set. The two-keyword form does not: every pair it writes has a
|
|
271
|
+
// single-keyword name already here, and the table and ruby display types stay
|
|
272
|
+
// out with the table formatting properties LOOK_EXCLUDED_PROPERTIES excludes.
|
|
273
|
+
["display", keywords("none", "block", "inline", "inline-block", "flow-root", "flex", "inline-flex", "grid", "inline-grid", "contents", "list-item")],
|
|
274
|
+
["isolation", keywords("auto", "isolate")],
|
|
275
|
+
["contain", keywords("none", "strict", "content", "size", "inline-size", "layout", "style", "paint")],
|
|
276
|
+
["backgroundSize", keywords("auto", "cover", "contain")],
|
|
277
|
+
// D67 rule 172: `backgroundPosition`, `transformOrigin` and `objectPosition`
|
|
278
|
+
// are one CSS grammar, so they are one `metric` kind reading one table. The
|
|
279
|
+
// two that used to hold five of the twenty-two placements refused
|
|
280
|
+
// `backgroundPosition = "top left"`, a value the property really has.
|
|
281
|
+
["backgroundPosition", keywords(...positionKeywords)],
|
|
282
|
+
["transformOrigin", keywords(...positionKeywords)],
|
|
283
|
+
["objectPosition", keywords(...positionKeywords)],
|
|
284
|
+
["transitionProperty", transitionPropertyKeywords],
|
|
285
|
+
["transitionTimingFunction", keywords(...LOOK_ANIMATION_EASINGS)],
|
|
286
|
+
["position", keywords("static", "relative", "absolute", "fixed", "sticky")],
|
|
287
|
+
["boxSizing", keywords("content-box", "border-box")],
|
|
288
|
+
["visibility", keywords("visible", "hidden", "collapse")],
|
|
289
|
+
["overflow", keywords("visible", "hidden", "clip", "scroll", "auto")],
|
|
290
|
+
["overflowX", keywords("visible", "hidden", "clip", "scroll", "auto")],
|
|
291
|
+
["overflowY", keywords("visible", "hidden", "clip", "scroll", "auto")],
|
|
292
|
+
["objectFit", keywords("fill", "contain", "cover", "none", "scale-down")],
|
|
293
|
+
["flexDirection", keywords("row", "row-reverse", "column", "column-reverse")],
|
|
294
|
+
["flexWrap", keywords("nowrap", "wrap", "wrap-reverse")],
|
|
295
|
+
// Each of the nine takes a different selection of the alignment groups, and
|
|
296
|
+
// the differences are the property's own grammar rather than an oversight:
|
|
297
|
+
// `justifyContent` has no baseline position, `alignContent` has no
|
|
298
|
+
// self-position, and the three `place*` shorthands take only what both of
|
|
299
|
+
// their halves take.
|
|
300
|
+
["alignItems", keywords("normal", "stretch", ...alignBaselinePositions, ...alignSelfPositions)],
|
|
301
|
+
["alignContent", keywords("normal", ...alignBaselinePositions, ...alignContentDistributions, ...alignContentPositions)],
|
|
302
|
+
["alignSelf", keywords("auto", "normal", "stretch", ...alignBaselinePositions, ...alignSelfPositions)],
|
|
303
|
+
["justifyItems", keywords("normal", "stretch", ...alignBaselinePositions, ...alignSelfPositions, "left", "right")],
|
|
304
|
+
["justifyContent", keywords("normal", ...alignContentDistributions, ...alignContentPositions, "left", "right")],
|
|
305
|
+
["justifySelf", keywords("auto", "normal", "stretch", ...alignBaselinePositions, ...alignSelfPositions, "left", "right")],
|
|
306
|
+
["placeItems", keywords("normal", "stretch", ...alignBaselinePositions, ...alignSelfPositions)],
|
|
307
|
+
["placeContent", keywords("normal", ...alignContentDistributions, ...alignContentPositions)],
|
|
308
|
+
["placeSelf", keywords("auto", "normal", "stretch", ...alignBaselinePositions, ...alignSelfPositions)],
|
|
309
|
+
// D67 rule 173: `all-scroll` was the one CSS Basic UI cursor keyword missing.
|
|
310
|
+
["cursor", keywords("auto", "default", "none", "context-menu", "help", "pointer", "progress", "wait", "cell", "crosshair", "text", "vertical-text", "alias", "copy", "move", "no-drop", "not-allowed", "grab", "grabbing", "all-scroll", "col-resize", "row-resize", "n-resize", "e-resize", "s-resize", "w-resize", "ne-resize", "nw-resize", "se-resize", "sw-resize", "ew-resize", "ns-resize", "nesw-resize", "nwse-resize", "zoom-in", "zoom-out")],
|
|
311
|
+
["textAlign", keywords("start", "end", "left", "right", "center", "justify", "match-parent")],
|
|
312
|
+
["textTransform", keywords("none", "capitalize", "uppercase", "lowercase", "full-width", "full-size-kana")],
|
|
313
|
+
["textDecoration", keywords(...textDecorationLineKeywords)],
|
|
314
|
+
["whiteSpace", keywords("normal", "pre", "nowrap", "pre-wrap", "pre-line", "break-spaces")],
|
|
315
|
+
["textOverflow", keywords("clip", "ellipsis")],
|
|
316
|
+
["textWrap", keywords("wrap", "nowrap", "balance", "pretty", "stable")],
|
|
317
|
+
["overflowWrap", keywords("normal", "break-word", "anywhere")],
|
|
318
|
+
["wordBreak", keywords("normal", "break-all", "keep-all", "break-word")],
|
|
319
|
+
["hyphens", keywords("none", "manual", "auto")],
|
|
320
|
+
["listStyle", keywords(...listStyleTypeKeywords, ...listStylePositionKeywords)],
|
|
321
|
+
["resize", keywords("none", "both", "horizontal", "vertical", "block", "inline")],
|
|
322
|
+
["pointerEvents", keywords("auto", "none", "visiblePainted", "visibleFill", "visibleStroke", "visible", "painted", "fill", "stroke", "all")],
|
|
323
|
+
["userSelect", keywords("auto", "text", "none", "contain", "all")],
|
|
324
|
+
["touchAction", keywords(...touchActionKeywords)],
|
|
325
|
+
["appearance", keywords("none", "auto", "textfield", "menulist-button")],
|
|
326
|
+
["scrollBehavior", keywords("auto", "smooth")],
|
|
327
|
+
["scrollbarWidth", keywords("auto", "thin", "none")],
|
|
328
|
+
["backgroundRepeat", keywords(...backgroundRepeatKeywords)],
|
|
329
|
+
["backgroundAttachment", keywords("scroll", "fixed", "local")],
|
|
330
|
+
["backgroundClip", keywords("border-box", "padding-box", "content-box", "text")],
|
|
331
|
+
["backgroundOrigin", keywords("border-box", "padding-box", "content-box")],
|
|
332
|
+
["fontStyle", keywords("normal", "italic", "oblique")],
|
|
333
|
+
["fontKerning", keywords("auto", "normal", "none")],
|
|
334
|
+
["fontOpticalSizing", keywords("auto", "none")],
|
|
335
|
+
["writingMode", keywords("horizontal-tb", "vertical-rl", "vertical-lr")],
|
|
336
|
+
["textOrientation", keywords("mixed", "upright", "sideways")],
|
|
337
|
+
["direction", keywords("ltr", "rtl")],
|
|
338
|
+
["unicodeBidi", keywords("normal", "embed", "isolate", "bidi-override", "isolate-override", "plaintext")],
|
|
339
|
+
// ── D65 rule 168 ────────────────────────────────────────────────────────
|
|
340
|
+
// Every entry below used to be absent, and a keyword property with no
|
|
341
|
+
// vocabulary of its own fell back to a shared list that knew nothing about
|
|
342
|
+
// it: it refused `borderStyle = "groove"` and accepted `strokeLinecap =
|
|
343
|
+
// "none"`, which reaches the browser as a declaration it discards. A value
|
|
344
|
+
// set that already exists in another table is read from that table rather
|
|
345
|
+
// than restated (D57 rule 134).
|
|
346
|
+
["gridAutoFlow", keywords(...lookValueOrderings(["row", "dense"]), ...lookValueOrderings(["column", "dense"]))],
|
|
347
|
+
["backgroundBlendMode", keywords("normal", "multiply", "screen", "overlay", "darken", "lighten", "color-dodge", "color-burn", "hard-light", "soft-light", "difference", "exclusion", "hue", "saturation", "color", "luminosity")],
|
|
348
|
+
// The five border-style properties and the border() builder's style argument
|
|
349
|
+
// are one vocabulary, so they are one table.
|
|
350
|
+
...["borderStyle", "borderTopStyle", "borderRightStyle", "borderBottomStyle", "borderLeftStyle"]
|
|
351
|
+
.map((property) => [property, keywords(...LOOK_BORDER_STYLE_NAMES)]),
|
|
352
|
+
// An outline takes the border styles plus `auto`, minus `hidden`: CSS Basic
|
|
353
|
+
// UI says `hidden` is not a legal outline style, and a border table read
|
|
354
|
+
// straight through would have published a twenty-seventh dead value.
|
|
355
|
+
["outlineStyle", keywords(...[...LOOK_BORDER_STYLE_NAMES].filter((style) => style !== "hidden"), "auto")],
|
|
356
|
+
["fontStretch", keywords("normal", "ultra-condensed", "extra-condensed", "condensed", "semi-condensed", "semi-expanded", "expanded", "extra-expanded", "ultra-expanded")],
|
|
357
|
+
// Every single-token value of the shorthand's six sub-properties: position,
|
|
358
|
+
// caps, numeric, alternates, ligatures, and East Asian.
|
|
359
|
+
["fontVariant", keywords("normal", "none", "sub", "super", "small-caps", "all-small-caps", "petite-caps", "all-petite-caps", "titling-caps", "unicase", "lining-nums", "oldstyle-nums", "proportional-nums", "tabular-nums", "diagonal-fractions", "stacked-fractions", "ordinal", "slashed-zero", "historical-forms", "common-ligatures", "no-common-ligatures", "discretionary-ligatures", "no-discretionary-ligatures", "historical-ligatures", "no-historical-ligatures", "contextual", "no-contextual", "jis78", "jis83", "jis90", "jis04", "simplified", "traditional", "full-width", "proportional-width", "ruby")],
|
|
360
|
+
["textDecorationLine", keywords(...textDecorationLineKeywords)],
|
|
361
|
+
["textDecorationStyle", keywords("solid", "double", "dotted", "dashed", "wavy")],
|
|
362
|
+
["textUnderlinePosition", keywords("auto", "from-font", ...lookValueOrderings(["under", "left"]), ...lookValueOrderings(["under", "right"]))],
|
|
363
|
+
["textRendering", keywords("auto", "optimizeSpeed", "optimizeLegibility", "geometricPrecision")],
|
|
364
|
+
["listStyleType", keywords(...listStyleTypeKeywords)],
|
|
365
|
+
["listStylePosition", keywords(...listStylePositionKeywords)],
|
|
366
|
+
["strokeLinecap", keywords("butt", "round", "square")],
|
|
367
|
+
["strokeLinejoin", keywords("miter", "round", "bevel")],
|
|
368
|
+
["colorScheme", keywords("normal", ...colorSchemeOrderings.flatMap((schemes) => [schemes, `only ${schemes}`, `${schemes} only`]))],
|
|
369
|
+
["scrollSnapAlign", keywords(...lookValueRepetitions(["none", "start", "end", "center"]))],
|
|
370
|
+
["scrollSnapStop", keywords("normal", "always")],
|
|
371
|
+
["scrollSnapType", keywords("none", ...scrollSnapAxes, ...lookValuePairs(scrollSnapAxes, ["mandatory", "proximity"]))],
|
|
372
|
+
["overscrollBehavior", keywords(...lookValueRepetitions(overscrollBehaviors))],
|
|
373
|
+
["overscrollBehaviorX", keywords(...overscrollBehaviors)],
|
|
374
|
+
["overscrollBehaviorY", keywords(...overscrollBehaviors)],
|
|
375
|
+
// ── D73 rule 187: the kinds outside `keyword` that also decide a string ────
|
|
376
|
+
// Each of these used to be answered by a 46-word list shared across every
|
|
377
|
+
// property that had no table of its own. That list both accepted values the
|
|
378
|
+
// property never had — `fontWeight = "circle"` compiled and reached CSS as a
|
|
379
|
+
// declaration the browser drops — and made the diagnostic promise "the closed
|
|
380
|
+
// fontWeight keywords", a table that did not exist. The sets below come from
|
|
381
|
+
// the CSS grammar of each property; what the grammar leaves open is recorded
|
|
382
|
+
// in LOOK_PARTIAL_KEYWORD_PROPERTIES rather than waved through.
|
|
383
|
+
// The shorthands whose non-keyword values are written with a builder. `none`
|
|
384
|
+
// is the whole of their keyword half.
|
|
385
|
+
...["border", "borderTop", "borderRight", "borderBottom", "borderLeft", "outline"]
|
|
386
|
+
.map((property) => [property, keywords("none")]),
|
|
387
|
+
["boxShadow", keywords("none")],
|
|
388
|
+
["textShadow", keywords("none")],
|
|
389
|
+
// CSS Fonts 4: the four named weights. The numeric half is the `number` this
|
|
390
|
+
// property's type already accepts.
|
|
391
|
+
["fontWeight", keywords("normal", "bold", "bolder", "lighter")],
|
|
392
|
+
["aspectRatio", keywords("auto")],
|
|
393
|
+
["scale", keywords("none")],
|
|
394
|
+
// CSS Flexbox 1: the single-token forms of the shorthand — `none`, and the
|
|
395
|
+
// `<'flex-basis'>` keywords, which are the sizing words.
|
|
396
|
+
["flex", keywords("none", "auto", "content", "min-content", "max-content", "fit-content")],
|
|
397
|
+
["lineHeight", keywords("normal")],
|
|
398
|
+
["rotate", keywords("none")],
|
|
399
|
+
// CSS Transitions: `<time>#`. There is no keyword half at all, so the closed
|
|
400
|
+
// set is the CSS-wide words and nothing else — which is a truthful answer
|
|
401
|
+
// where "one of the closed keywords" was not.
|
|
402
|
+
["transitionDuration", keywords()],
|
|
403
|
+
["transitionDelay", keywords()],
|
|
404
|
+
["transition", keywords("none")],
|
|
405
|
+
["opacity", keywords()],
|
|
406
|
+
["zIndex", keywords("auto")],
|
|
407
|
+
["flexGrow", keywords()],
|
|
408
|
+
["flexShrink", keywords()],
|
|
409
|
+
["order", keywords()],
|
|
410
|
+
["tabSize", keywords()],
|
|
411
|
+
// CSS Grid: a template is written with `tracks(...)`, so `none` is its only
|
|
412
|
+
// text. An implicit track takes the three `<track-size>` keywords.
|
|
413
|
+
["gridTemplateColumns", keywords("none")],
|
|
414
|
+
["gridTemplateRows", keywords("none")],
|
|
415
|
+
["gridAutoColumns", keywords("auto", "min-content", "max-content")],
|
|
416
|
+
["gridAutoRows", keywords("auto", "min-content", "max-content")],
|
|
417
|
+
]);
|
|
418
|
+
/**
|
|
419
|
+
* D73 rule 187 — every kind whose string values are decided per property. D65
|
|
420
|
+
* rule 168 drew this line at `kind === "keyword"`, which was that wave's
|
|
421
|
+
* boundary rather than the principle: the principle is that publishing a
|
|
422
|
+
* surface which cannot be reached, or which swallows a false value, is worse
|
|
423
|
+
* than not publishing it (D50 rule 92), and that principle does not know about
|
|
424
|
+
* kinds. `metric` is absent because it answers with the sizing words when it
|
|
425
|
+
* has no table of its own, and `color`, `background` and `image` are absent
|
|
426
|
+
* because their diagnostics name a real vocabulary — a color keyword, a builder
|
|
427
|
+
* — rather than a table. `text`, `filter`, `transform` and `animation` accept
|
|
428
|
+
* arbitrary text by construction.
|
|
429
|
+
*/
|
|
430
|
+
export const LOOK_KEYWORD_DECIDED_KINDS = new Set([
|
|
431
|
+
"keyword", "border", "shadow", "number", "number-keyword", "line-height", "angle", "duration", "track", "transition",
|
|
432
|
+
]);
|
|
433
|
+
/**
|
|
434
|
+
* D65 rule 168, widened by D73 rule 187 — a property whose values are decided
|
|
435
|
+
* per property carries its own closed set or the module refuses to load. The
|
|
436
|
+
* shared fallback vocabulary this replaces was worse than a missing check: it
|
|
437
|
+
* decided values for a property it had never heard of, so the same table that
|
|
438
|
+
* rejected `listStyleType = "upper-roman"` accepted `colorScheme = "none"` and
|
|
439
|
+
* `fontWeight = "circle"` and emitted them as declarations the browser drops.
|
|
440
|
+
* A published name whose values are decided by a table that does not know the
|
|
441
|
+
* property is not a checked property, so this is a load-time fact rather than
|
|
442
|
+
* a test — the same shape as the value-kind invariant above.
|
|
443
|
+
*/
|
|
444
|
+
for (const [property, kind] of LOOK_PROPERTY_VALUE_KINDS) {
|
|
445
|
+
if (LOOK_KEYWORD_DECIDED_KINDS.has(kind) && !LOOK_PROPERTY_KEYWORDS.has(property)) {
|
|
446
|
+
throw new Error(`Look property '${property}' accepts string keywords and has no closed keyword set`);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
/** A property's own closed values, without the CSS-wide keywords every property shares. */
|
|
450
|
+
export function lookOwnKeywords(property) {
|
|
451
|
+
const values = LOOK_PROPERTY_KEYWORDS.get(property);
|
|
452
|
+
return values === undefined ? [] : [...values].filter((value) => !cssWideKeywordSet.has(value));
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* D67 rule 174 — how many of a property's own values a diagnostic writes out
|
|
456
|
+
* before it names the shape of the set instead.
|
|
457
|
+
*
|
|
458
|
+
* The number is not load-bearing at its exact value: the published tables fall
|
|
459
|
+
* into two clumps with nothing between them, the largest single-grammar set
|
|
460
|
+
* holding twenty-two values and the next set up holding thirty-five. Anything
|
|
461
|
+
* in that gap draws the same line. What matters is that a set on the far side
|
|
462
|
+
* of it still says something an author can act on, which is what
|
|
463
|
+
* LOOK_LARGE_KEYWORD_SETS below is for.
|
|
464
|
+
*/
|
|
465
|
+
export const LOOK_KEYWORD_LISTING_LIMIT = 24;
|
|
466
|
+
/**
|
|
467
|
+
* What a vocabulary too long to write out holds, in the terms the author would
|
|
468
|
+
* look it up by. A diagnostic reads this when the value it rejected is not a
|
|
469
|
+
* near miss of anything, so that "one of the closed keywords" is never the
|
|
470
|
+
* whole of the answer (D67 rule 174).
|
|
471
|
+
*/
|
|
472
|
+
export const LOOK_LARGE_KEYWORD_SETS = new Map([
|
|
473
|
+
["cursor", "the CSS Basic UI cursor keywords, including the eight directional and four bidirectional resize cursors"],
|
|
474
|
+
["fontVariant", "the single-token values of the six font-variant feature groups: position, caps, numeric, alternates, ligatures, and East Asian"],
|
|
475
|
+
["listStyleType", "the predefined counter styles of CSS Counter Styles 3, by numeric, alphabetic, symbolic, fixed, and complex system"],
|
|
476
|
+
["listStyle", "the predefined counter styles of CSS Counter Styles 3 plus the inside and outside marker positions"],
|
|
477
|
+
["transitionProperty", "none, all, and the CSS spelling of every animatable Look property"],
|
|
478
|
+
]);
|
|
479
|
+
for (const [property] of LOOK_PROPERTY_KEYWORDS) {
|
|
480
|
+
const own = lookOwnKeywords(property);
|
|
481
|
+
if (own.length > LOOK_KEYWORD_LISTING_LIMIT && !LOOK_LARGE_KEYWORD_SETS.has(property)) {
|
|
482
|
+
throw new Error(`Look property '${property}' publishes ${own.length} keywords, more than a diagnostic writes out, and records no description of what the set holds`);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
for (const [property] of LOOK_LARGE_KEYWORD_SETS) {
|
|
486
|
+
if (lookOwnKeywords(property).length <= LOOK_KEYWORD_LISTING_LIMIT) {
|
|
487
|
+
throw new Error(`Look property '${property}' describes its keyword set but is small enough to write out, so the description would never be read`);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* D65 rule 169 — what a partly closable property leaves out, and why.
|
|
492
|
+
*
|
|
493
|
+
* Some CSS value spaces cannot be written as a set: `listStyleType` reaches
|
|
494
|
+
* into `@counter-style` names, `fontVariant` into a combination of six feature
|
|
495
|
+
* groups, `fontStretch` into percentages. Such a property publishes the subset it
|
|
496
|
+
* can close and records the remainder here, so the boundary is visible in the
|
|
497
|
+
* table and in the diagnostic instead of being waved through by a fallback
|
|
498
|
+
* vocabulary. This is the per-value sibling of LOOK_EXCLUDED_PROPERTIES, which
|
|
499
|
+
* records the properties left out whole.
|
|
500
|
+
*/
|
|
501
|
+
export const LOOK_PARTIAL_KEYWORD_PROPERTIES = new Map([
|
|
502
|
+
// D67 rule 172 revoked `objectPosition`'s record: the lengths it named as
|
|
503
|
+
// excluded are the unit half of the `metric` kind it now shares with the
|
|
504
|
+
// other two `<position>` properties, so nothing is left out to record.
|
|
505
|
+
["contain", "Combinations of size, layout, style and paint are outside the closed set, which holds the single tokens plus the named strict and content shorthands"],
|
|
506
|
+
["backgroundBlendMode", "A comma-separated per-layer blend list is outside the closed set, because a checked Look background is one layer"],
|
|
507
|
+
["borderStyle", "The one-to-four value per-side form is outside the closed set; borderTopStyle, borderRightStyle, borderBottomStyle and borderLeftStyle write it"],
|
|
508
|
+
["fontStretch", "Percentage font widths are outside the closed set, which holds the named widths"],
|
|
509
|
+
["fontVariant", "Combining feature groups in one value, and the annotation(), character-variant(), ornaments(), styleset() and swash() notations, are outside the closed set, which holds each group's single-token values; fontFeatureSettings carries a combination"],
|
|
510
|
+
["textDecorationLine", "The blink line is outside the closed set: browsers parse it and draw nothing, so it is a declaration with no effect. The spelling-error and grammar-error lines are outside it too, because a document cannot ask for them the way a spell checker does"],
|
|
511
|
+
["strokeLinejoin", "The SVG2 arcs and miter-clip joins are outside the closed set, because no browser implements them"],
|
|
512
|
+
["textDecoration", "The style, color and thickness parts of the shorthand are outside the closed set; textDecorationStyle, textDecorationColor and textDecorationThickness write them"],
|
|
513
|
+
["listStyle", "The image part and multi-part combinations of the shorthand are outside the closed set; listStyleType, listStylePosition and listStyleImage write them"],
|
|
514
|
+
["listStyleType", "A literal string marker and a custom @counter-style name are outside the closed set, which holds the predefined counter styles"],
|
|
515
|
+
// ── D67 rule 173 ────────────────────────────────────────────────────────
|
|
516
|
+
// The rest of the seventy-seven, recorded rather than completed. Rule 173's
|
|
517
|
+
// deliverable is the boundary, not the value count: a property may publish a
|
|
518
|
+
// subset of its CSS grammar as long as the subset's edge is written down here
|
|
519
|
+
// and the diagnostic reads it out. Each entry below names a value space that
|
|
520
|
+
// is open (a string, an angle, a custom ident), redundant with a spelling the
|
|
521
|
+
// set already holds, owned by another property, or unimplemented.
|
|
522
|
+
["display", "The two-keyword <display-outside> <display-inside> form is outside the closed set, and writes nothing the single-keyword names here do not: block flow is block, inline flow-root is inline-block. The table, ruby and run-in display types are outside it too, with the table formatting properties and for the same reason"],
|
|
523
|
+
["overflow", "The two-value x y form is outside the closed set; overflowX and overflowY write it"],
|
|
524
|
+
["fontStyle", "An oblique slant angle such as 'oblique 14deg' is outside the closed set, which holds the named slants"],
|
|
525
|
+
["textOverflow", "A literal ellipsis string, and the two-value start end form, are outside the closed set, which holds clip and ellipsis"],
|
|
526
|
+
["textAlign", "The justify-all keyword is outside the closed set, because no browser justifies the last line for it"],
|
|
527
|
+
["textTransform", "Combining a case transform with full-width or full-size-kana in one value is outside the closed set, which holds each of them alone"],
|
|
528
|
+
["writingMode", "The sideways-rl and sideways-lr modes are outside the closed set, because only one browser engine implements them"],
|
|
529
|
+
["touchAction", "Combining a horizontal pan, a vertical pan and pinch-zoom in one value is outside the closed set, which holds each of them alone"],
|
|
530
|
+
["appearance", "The compat-auto aliases such as button, checkbox and menulist are outside the closed set, because each of them renders exactly as auto"],
|
|
531
|
+
["colorScheme", "A custom scheme ident is outside the closed set, which holds the two schemes 'if scheme.dark:' conditions on"],
|
|
532
|
+
["cursor", "A url() cursor image and its fallback list are outside the closed set, which holds the keyword cursors"],
|
|
533
|
+
// D49 gave `animate(easing=)` and this property one easing table, so the
|
|
534
|
+
// functions absent from it are absent on purpose. Recording that is how the
|
|
535
|
+
// closure stays legible without being mistaken for a gap to fill.
|
|
536
|
+
["transitionTimingFunction", "The cubic-bezier(), steps() and linear() easing functions are outside the closed set, which is the same easing table animate(easing=) reads"],
|
|
537
|
+
// D60 rule 150 derived this set from the Look property table minus the
|
|
538
|
+
// properties that do not interpolate. The subtraction is the boundary.
|
|
539
|
+
["transitionProperty", "A property whose value does not interpolate is outside the closed set, for the same reason a 'keyframes:' stop rejects it"],
|
|
540
|
+
// The nine Box Alignment properties. Each records the overflow-position
|
|
541
|
+
// prefixes; the three shorthands also record the two-value form, the way
|
|
542
|
+
// borderStyle records its per-side form.
|
|
543
|
+
...["alignItems", "alignContent", "alignSelf", "justifyItems", "justifyContent", "justifySelf"]
|
|
544
|
+
.map((property) => [property, "The safe and unsafe overflow-position prefixes are outside the closed set, which holds the alignments themselves"]),
|
|
545
|
+
["placeItems", "The two-value form and the safe and unsafe overflow-position prefixes are outside the closed set; alignItems and justifyItems write the two halves separately"],
|
|
546
|
+
["placeContent", "The two-value form and the safe and unsafe overflow-position prefixes are outside the closed set; alignContent and justifyContent write the two halves separately"],
|
|
547
|
+
["placeSelf", "The two-value form and the safe and unsafe overflow-position prefixes are outside the closed set; alignSelf and justifySelf write the two halves separately"],
|
|
548
|
+
// The background layer properties. A checked Look background is one layer,
|
|
549
|
+
// which is one design fact met in five places, so it is recorded in all five.
|
|
550
|
+
...["backgroundRepeat", "backgroundAttachment", "backgroundClip", "backgroundOrigin"]
|
|
551
|
+
.map((property) => [property, "A comma-separated per-layer list is outside the closed set, because a checked Look background is one layer"]),
|
|
552
|
+
// ── D73 rule 187: what the newly closed kinds leave open ───────────────────
|
|
553
|
+
// Each of these has a real CSS value space that no set can hold, and each has
|
|
554
|
+
// a checked Look spelling that reaches it. Recording that is what keeps the
|
|
555
|
+
// closure legible rather than looking like a gap to fill.
|
|
556
|
+
...["border", "borderTop", "borderRight", "borderBottom", "borderLeft", "outline"]
|
|
557
|
+
.map((property) => [property, "The width, style and color halves are outside the closed set; they are written with the border(width, color, style) builder"]),
|
|
558
|
+
...["boxShadow", "textShadow"]
|
|
559
|
+
.map((property) => [property, "The offsets, blur, spread and color are outside the closed set; they are written with the shadow(x, y, blur, color) builder"]),
|
|
560
|
+
["aspectRatio", "The <ratio> form such as 16 / 9 is outside the closed set; write the ratio as a number"],
|
|
561
|
+
["scale", "The two- and three-axis forms are outside the closed set; a single number scales every axis"],
|
|
562
|
+
["flex", "The multi-value form such as 1 1 auto is outside the closed set; write flexGrow, flexShrink and flexBasis separately"],
|
|
563
|
+
["rotate", "The axis forms such as x 45deg are outside the closed set; a plain angle rotates in the plane"],
|
|
564
|
+
["transition", "Every transition other than none is outside the closed set; it is written with the transition(property, duration, easing, delay) builder, for the same reason a multi-part border string is"],
|
|
565
|
+
...["gridTemplateColumns", "gridTemplateRows"]
|
|
566
|
+
.map((property) => [property, "Track lists, subgrid and masonry are outside the closed set; a track list is written with the tracks(...) builder"]),
|
|
567
|
+
...["gridAutoColumns", "gridAutoRows"]
|
|
568
|
+
.map((property) => [property, "The minmax() and fit-content() track functions are outside the closed set; they are written with the minmax(...) builder inside tracks(...)"]),
|
|
569
|
+
]);
|
|
570
|
+
for (const [property] of LOOK_PARTIAL_KEYWORD_PROPERTIES) {
|
|
571
|
+
if (!LOOK_PROPERTY_KEYWORDS.has(property))
|
|
572
|
+
throw new Error(`Look property '${property}' records an excluded value space but publishes no keyword set`);
|
|
573
|
+
}
|
|
574
|
+
/** Real CSS properties deliberately outside the checked Look domain. */
|
|
575
|
+
export const LOOK_EXCLUDED_PROPERTIES = new Map([
|
|
576
|
+
["float", "legacy float layout is outside the Grid and Flex layout model"],
|
|
577
|
+
["clear", "legacy float clearing is outside the Grid and Flex layout model"],
|
|
578
|
+
...["tableLayout", "borderCollapse", "borderSpacing", "captionSide", "emptyCells"].map((name) => [name, "table formatting properties remain excluded until a typed table-layout contract has evidence"]),
|
|
579
|
+
...["columns", "columnCount", "columnWidth", "columnFill", "columnRule", "columnRuleColor", "columnRuleStyle", "columnRuleWidth", "columnSpan"].map((name) => [name, "multi-column layout remains excluded until its value and fragmentation model is typed"]),
|
|
580
|
+
...["animationName", "animationDuration", "animationTimingFunction", "animationDelay", "animationIterationCount", "animationDirection", "animationFillMode", "animationPlayState", "animationTimeline", "animationRangeStart", "animationRangeEnd"].map((name) => [name, "animation longhands are owned by keyframes plus animate()"]),
|
|
581
|
+
...["counterIncrement", "counterReset", "counterSet", "quotes"].map((name) => [name, "generated-content counters and quoting are not modeled by checked Look values"]),
|
|
582
|
+
...["breakAfter", "breakBefore", "breakInside", "orphans", "widows"].map((name) => [name, "paged and fragmented media are outside the Web application target"]),
|
|
583
|
+
]);
|
|
584
|
+
export function cssPropertyName(name) {
|
|
585
|
+
return name.replace(/[A-Z]/gu, (character) => `-${character.toLowerCase()}`);
|
|
586
|
+
}
|
|
587
|
+
function nameDistance(left, right) {
|
|
588
|
+
if (left.length === right.length) {
|
|
589
|
+
const mismatches = [];
|
|
590
|
+
for (let index = 0; index < left.length; index += 1)
|
|
591
|
+
if (left[index] !== right[index])
|
|
592
|
+
mismatches.push(index);
|
|
593
|
+
if (mismatches.length === 2 && mismatches[1] === mismatches[0] + 1
|
|
594
|
+
&& left[mismatches[0]] === right[mismatches[1]] && left[mismatches[1]] === right[mismatches[0]])
|
|
595
|
+
return 1;
|
|
596
|
+
}
|
|
597
|
+
const previous = Array.from({ length: right.length + 1 }, (_, index) => index);
|
|
598
|
+
for (let row = 1; row <= left.length; row += 1) {
|
|
599
|
+
let diagonal = previous[0];
|
|
600
|
+
previous[0] = row;
|
|
601
|
+
for (let column = 1; column <= right.length; column += 1) {
|
|
602
|
+
const candidate = Math.min(previous[column] + 1, previous[column - 1] + 1, diagonal + (left[row - 1] === right[column - 1] ? 0 : 1));
|
|
603
|
+
diagonal = previous[column];
|
|
604
|
+
previous[column] = candidate;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
return previous[right.length];
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* The closest vocabulary entry to a misspelling, or null when nothing is close
|
|
611
|
+
* enough to name. Case differences and one or two edits count as near misses;
|
|
612
|
+
* anything further apart is a different word and gets the full vocabulary.
|
|
613
|
+
*/
|
|
614
|
+
export function nearestLookName(name, vocabulary) {
|
|
615
|
+
let best = null;
|
|
616
|
+
let bestDistance = Number.POSITIVE_INFINITY;
|
|
617
|
+
const limit = 2;
|
|
618
|
+
for (const candidate of vocabulary) {
|
|
619
|
+
const distance = nameDistance(name.toLowerCase(), candidate.toLowerCase());
|
|
620
|
+
if (distance < bestDistance || (distance === bestDistance && best !== null && candidate < best)) {
|
|
621
|
+
best = candidate;
|
|
622
|
+
bestDistance = distance;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
return best !== null && bestDistance <= limit ? best : null;
|
|
626
|
+
}
|
|
627
|
+
//# sourceMappingURL=look.js.map
|