@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.
Files changed (95) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +89 -0
  3. package/dist/analyzer.d.ts +302 -0
  4. package/dist/analyzer.d.ts.map +1 -0
  5. package/dist/analyzer.js +2931 -0
  6. package/dist/analyzer.js.map +1 -0
  7. package/dist/ast.d.ts +220 -0
  8. package/dist/ast.d.ts.map +1 -0
  9. package/dist/ast.js +102 -0
  10. package/dist/ast.js.map +1 -0
  11. package/dist/browser-test.d.ts +29 -0
  12. package/dist/browser-test.d.ts.map +1 -0
  13. package/dist/browser-test.js +33 -0
  14. package/dist/browser-test.js.map +1 -0
  15. package/dist/compiler.d.ts +9 -0
  16. package/dist/compiler.d.ts.map +1 -0
  17. package/dist/compiler.js +715 -0
  18. package/dist/compiler.js.map +1 -0
  19. package/dist/css-tokens.d.ts +46 -0
  20. package/dist/css-tokens.d.ts.map +1 -0
  21. package/dist/css-tokens.js +363 -0
  22. package/dist/css-tokens.js.map +1 -0
  23. package/dist/editor.d.ts +3 -0
  24. package/dist/editor.d.ts.map +1 -0
  25. package/dist/editor.js +147 -0
  26. package/dist/editor.js.map +1 -0
  27. package/dist/elements.d.ts +11 -0
  28. package/dist/elements.d.ts.map +1 -0
  29. package/dist/elements.js +24 -0
  30. package/dist/elements.js.map +1 -0
  31. package/dist/emitter.d.ts +79 -0
  32. package/dist/emitter.d.ts.map +1 -0
  33. package/dist/emitter.js +2699 -0
  34. package/dist/emitter.js.map +1 -0
  35. package/dist/host.d.ts +7 -0
  36. package/dist/host.d.ts.map +1 -0
  37. package/dist/host.js +224 -0
  38. package/dist/host.js.map +1 -0
  39. package/dist/index.d.ts +11 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +23 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/inspection.d.ts +3 -0
  44. package/dist/inspection.d.ts.map +1 -0
  45. package/dist/inspection.js +82 -0
  46. package/dist/inspection.js.map +1 -0
  47. package/dist/keyframes.d.ts +16 -0
  48. package/dist/keyframes.d.ts.map +1 -0
  49. package/dist/keyframes.js +29 -0
  50. package/dist/keyframes.js.map +1 -0
  51. package/dist/lexer.d.ts +85 -0
  52. package/dist/lexer.d.ts.map +1 -0
  53. package/dist/lexer.js +422 -0
  54. package/dist/lexer.js.map +1 -0
  55. package/dist/look-static.d.ts +31 -0
  56. package/dist/look-static.d.ts.map +1 -0
  57. package/dist/look-static.js +266 -0
  58. package/dist/look-static.js.map +1 -0
  59. package/dist/look.d.ts +134 -0
  60. package/dist/look.d.ts.map +1 -0
  61. package/dist/look.js +627 -0
  62. package/dist/look.js.map +1 -0
  63. package/dist/parser.d.ts +71 -0
  64. package/dist/parser.d.ts.map +1 -0
  65. package/dist/parser.js +976 -0
  66. package/dist/parser.js.map +1 -0
  67. package/dist/project-config.d.ts +33 -0
  68. package/dist/project-config.d.ts.map +1 -0
  69. package/dist/project-config.js +199 -0
  70. package/dist/project-config.js.map +1 -0
  71. package/dist/runtime-foundation.d.ts +7 -0
  72. package/dist/runtime-foundation.d.ts.map +1 -0
  73. package/dist/runtime-foundation.js +1245 -0
  74. package/dist/runtime-foundation.js.map +1 -0
  75. package/dist/runtime.d.ts +7 -0
  76. package/dist/runtime.d.ts.map +1 -0
  77. package/dist/runtime.js +3444 -0
  78. package/dist/runtime.js.map +1 -0
  79. package/dist/semantic.d.ts +3 -0
  80. package/dist/semantic.d.ts.map +1 -0
  81. package/dist/semantic.js +150 -0
  82. package/dist/semantic.js.map +1 -0
  83. package/dist/types.d.ts +56 -0
  84. package/dist/types.d.ts.map +1 -0
  85. package/dist/types.js +178 -0
  86. package/dist/types.js.map +1 -0
  87. package/dist/websocket-runtime.d.ts +2 -0
  88. package/dist/websocket-runtime.d.ts.map +1 -0
  89. package/dist/websocket-runtime.js +30 -0
  90. package/dist/websocket-runtime.js.map +1 -0
  91. package/dist/worker-runtime.d.ts +2 -0
  92. package/dist/worker-runtime.d.ts.map +1 -0
  93. package/dist/worker-runtime.js +72 -0
  94. package/dist/worker-runtime.js.map +1 -0
  95. 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