@@ -0,0 +1,202 @@
1+//! The attribute vocabulary.
2+//!
3+//! An attribute in `[...]` is one of three things:
4+//! - one of htmlang's own attributes, which mean more than a single CSS
5+//! property (`spacing`, `center-x`, `rounded`, ...), handled in codegen;
6+//! - a standard CSS property, copied into the generated CSS unchanged;
7+//! - an HTML attribute, emitted on the element.
8+
9+/// htmlang's own attributes (those that are neither a CSS property nor an
10+/// HTML attribute).
11+pub const HTMLANG_ATTRIBUTES: &[&str] = &[
12+ "align-bottom", "align-left", "align-right", "align-top", "backdrop-blur", "blur", "bold",
13+ "center-x", "center-y", "col-span", "critical", "gap-x", "gap-y", "gradient", "grid-cols",
14+ "grid-rows", "inline", "italic", "margin-x", "margin-y", "no-scrollbar", "ordered",
15+ "padding-x", "padding-y", "responsive", "rounded", "row-span", "shadow", "skeleton",
16+ "spacing", "truncate", "underline",
17+];
18+
19+/// Standard CSS properties, sorted. Any of these can be written as an
20+/// attribute and is copied into the generated CSS as-is.
21+pub const CSS_PROPERTIES: &[&str] = &[
22+ "accent-color", "align-content", "align-items", "align-self", "all", "anchor-name",
23+ "animation", "animation-composition", "animation-delay", "animation-direction",
24+ "animation-duration", "animation-fill-mode", "animation-iteration-count", "animation-name",
25+ "animation-play-state", "animation-range", "animation-range-end", "animation-range-start",
26+ "animation-timeline", "animation-timing-function", "appearance", "aspect-ratio",
27+ "backdrop-filter", "backface-visibility", "background", "background-attachment",
28+ "background-blend-mode", "background-clip", "background-color", "background-image",
29+ "background-origin", "background-position", "background-position-x",
30+ "background-position-y", "background-repeat", "background-size", "block-size", "border",
31+ "border-block", "border-block-color", "border-block-end", "border-block-end-color",
32+ "border-block-end-style", "border-block-end-width", "border-block-start",
33+ "border-block-start-color", "border-block-start-style", "border-block-start-width",
34+ "border-block-style", "border-block-width", "border-bottom", "border-bottom-color",
35+ "border-bottom-left-radius", "border-bottom-right-radius", "border-bottom-style",
36+ "border-bottom-width", "border-collapse", "border-color", "border-end-end-radius",
37+ "border-end-start-radius", "border-image", "border-image-outset", "border-image-repeat",
38+ "border-image-slice", "border-image-source", "border-image-width", "border-inline",
39+ "border-inline-color", "border-inline-end", "border-inline-end-color",
40+ "border-inline-end-style", "border-inline-end-width", "border-inline-start",
41+ "border-inline-start-color", "border-inline-start-style", "border-inline-start-width",
42+ "border-inline-style", "border-inline-width", "border-left", "border-left-color",
43+ "border-left-style", "border-left-width", "border-radius", "border-right",
44+ "border-right-color", "border-right-style", "border-right-width", "border-spacing",
45+ "border-start-end-radius", "border-start-start-radius", "border-style", "border-top",
46+ "border-top-color", "border-top-left-radius", "border-top-right-radius", "border-top-style",
47+ "border-top-width", "border-width", "bottom", "box-decoration-break", "box-shadow",
48+ "box-sizing", "break-after", "break-before", "break-inside", "caption-side", "caret-color",
49+ "clear", "clip", "clip-path", "color", "color-scheme", "column-count", "column-fill",
50+ "column-gap", "column-rule", "column-rule-color", "column-rule-style", "column-rule-width",
51+ "column-span", "column-width", "columns", "contain", "contain-intrinsic-block-size",
52+ "contain-intrinsic-height", "contain-intrinsic-inline-size", "contain-intrinsic-size",
53+ "contain-intrinsic-width", "container", "container-name", "container-type", "content",
54+ "content-visibility", "counter-increment", "counter-reset", "counter-set", "cursor",
55+ "direction", "display", "empty-cells", "field-sizing", "filter", "flex", "flex-basis",
56+ "flex-direction", "flex-flow", "flex-grow", "flex-shrink", "flex-wrap", "float", "font",
57+ "font-family", "font-feature-settings", "font-kerning", "font-language-override",
58+ "font-optical-sizing", "font-palette", "font-size", "font-size-adjust", "font-stretch",
59+ "font-style", "font-synthesis", "font-variant", "font-variant-alternates",
60+ "font-variant-caps", "font-variant-east-asian", "font-variant-ligatures",
61+ "font-variant-numeric", "font-variant-position", "font-variation-settings", "font-weight",
62+ "forced-color-adjust", "gap", "grid", "grid-area", "grid-auto-columns", "grid-auto-flow",
63+ "grid-auto-rows", "grid-column", "grid-column-end", "grid-column-start", "grid-row",
64+ "grid-row-end", "grid-row-start", "grid-template", "grid-template-areas",
65+ "grid-template-columns", "grid-template-rows", "hanging-punctuation", "height",
66+ "hyphenate-character", "hyphens", "image-orientation", "image-rendering", "initial-letter",
67+ "inline-size", "inset", "inset-block", "inset-block-end", "inset-block-start",
68+ "inset-inline", "inset-inline-end", "inset-inline-start", "interpolate-size", "isolation",
69+ "justify-content", "justify-items", "justify-self", "left", "letter-spacing", "line-break",
70+ "line-clamp", "line-height", "list-style", "list-style-image", "list-style-position",
71+ "list-style-type", "margin", "margin-block", "margin-block-end", "margin-block-start",
72+ "margin-bottom", "margin-inline", "margin-inline-end", "margin-inline-start", "margin-left",
73+ "margin-right", "margin-top", "mask", "mask-clip", "mask-composite", "mask-image",
74+ "mask-mode", "mask-origin", "mask-position", "mask-repeat", "mask-size", "mask-type",
75+ "math-depth", "math-style", "max-block-size", "max-height", "max-inline-size", "max-width",
76+ "min-block-size", "min-height", "min-inline-size", "min-width", "mix-blend-mode",
77+ "object-fit", "object-position", "offset", "offset-anchor", "offset-distance",
78+ "offset-path", "offset-position", "offset-rotate", "opacity", "order", "orphans", "outline",
79+ "outline-color", "outline-offset", "outline-style", "outline-width", "overflow",
80+ "overflow-anchor", "overflow-block", "overflow-clip-margin", "overflow-inline",
81+ "overflow-wrap", "overflow-x", "overflow-y", "overscroll-behavior",
82+ "overscroll-behavior-block", "overscroll-behavior-inline", "overscroll-behavior-x",
83+ "overscroll-behavior-y", "padding", "padding-block", "padding-block-end",
84+ "padding-block-start", "padding-bottom", "padding-inline", "padding-inline-end",
85+ "padding-inline-start", "padding-left", "padding-right", "padding-top", "page",
86+ "page-break-after", "page-break-before", "page-break-inside", "paint-order", "perspective",
87+ "perspective-origin", "place-content", "place-items", "place-self", "pointer-events",
88+ "position", "position-anchor", "position-area", "position-try", "position-try-fallbacks",
89+ "position-visibility", "print-color-adjust", "quotes", "resize", "right", "rotate",
90+ "row-gap", "ruby-align", "ruby-position", "scale", "scroll-behavior", "scroll-margin",
91+ "scroll-margin-block", "scroll-margin-block-end", "scroll-margin-block-start",
92+ "scroll-margin-bottom", "scroll-margin-inline", "scroll-margin-inline-end",
93+ "scroll-margin-inline-start", "scroll-margin-left", "scroll-margin-right",
94+ "scroll-margin-top", "scroll-padding", "scroll-padding-block", "scroll-padding-block-end",
95+ "scroll-padding-block-start", "scroll-padding-bottom", "scroll-padding-inline",
96+ "scroll-padding-inline-end", "scroll-padding-inline-start", "scroll-padding-left",
97+ "scroll-padding-right", "scroll-padding-top", "scroll-snap-align", "scroll-snap-stop",
98+ "scroll-snap-type", "scroll-timeline", "scroll-timeline-axis", "scroll-timeline-name",
99+ "scrollbar-color", "scrollbar-gutter", "scrollbar-width", "shape-image-threshold",
100+ "shape-margin", "shape-outside", "tab-size", "table-layout", "text-align",
101+ "text-align-last", "text-anchor", "text-box", "text-box-edge", "text-box-trim",
102+ "text-combine-upright", "text-decoration", "text-decoration-color", "text-decoration-line",
103+ "text-decoration-skip-ink", "text-decoration-style", "text-decoration-thickness",
104+ "text-emphasis", "text-emphasis-color", "text-emphasis-position", "text-emphasis-style",
105+ "text-indent", "text-justify", "text-orientation", "text-overflow", "text-rendering",
106+ "text-shadow", "text-transform", "text-underline-offset", "text-underline-position",
107+ "text-wrap", "text-wrap-mode", "text-wrap-style", "timeline-scope", "top", "touch-action",
108+ "transform", "transform-box", "transform-origin", "transform-style", "transition",
109+ "transition-behavior", "transition-delay", "transition-duration", "transition-property",
110+ "transition-timing-function", "translate", "unicode-bidi", "user-select", "vertical-align",
111+ "view-timeline", "view-timeline-axis", "view-timeline-inset", "view-timeline-name",
112+ "view-transition-class", "view-transition-name", "visibility", "white-space",
113+ "white-space-collapse", "widows", "width", "will-change", "word-break", "word-spacing",
114+ "writing-mode", "z-index", "zoom",
115+];
116+
117+/// HTML attributes emitted on any element when present.
118+pub const HTML_PASSTHROUGH_ATTRS: &[&str] = &[
119+ "action", "alt", "aria-atomic", "aria-live", "aria-relevant", "autocomplete", "autofocus",
120+ "autoplay", "blocking", "checked", "cols", "colspan", "controls", "datetime", "decoding",
121+ "disabled", "enterkeyhint", "fetchpriority", "for", "high", "inputmode", "list", "loading",
122+ "loop", "low", "max", "maxlength", "media", "method", "min", "multiple", "muted", "name",
123+ "novalidate", "open", "optimum", "pattern", "placeholder", "playsinline", "popover",
124+ "popovertarget", "popovertargetaction", "poster", "preload", "required", "role", "rows",
125+ "rowspan", "scope", "sizes", "spellcheck", "src", "srcset", "step", "tabindex", "title",
126+ "translate", "type", "value",
127+];
128+
129+/// Boolean HTML attributes (rendered without a value, e.g. `<input disabled>`).
130+pub const BOOLEAN_HTML_ATTRS: &[&str] = &[
131+ "disabled", "required", "checked", "multiple", "controls", "autoplay", "loop", "muted",
132+ "playsinline", "open", "novalidate", "autofocus", "defer", "async", "nomodule", "popover",
133+];
134+
135+/// Every HTML attribute htmlang knows, including ones only certain elements
136+/// emit (`href`, `sandbox`, `defer`, ...).
137+pub const HTML_ATTRIBUTES: &[&str] = &[
138+ "abbr", "accept", "action", "allow", "allowfullscreen", "alt", "aria-atomic", "aria-live",
139+ "aria-relevant", "async", "autocomplete", "autofocus", "autoplay", "blocking", "charset",
140+ "checked", "cite", "class", "cols", "colspan", "content", "contenteditable", "controls",
141+ "crossorigin", "datetime", "decoding", "defer", "dir", "disabled", "download", "draggable",
142+ "enctype", "enterkeyhint", "fetchpriority", "for", "form", "formaction", "formmethod",
143+ "formtarget", "headers", "height", "hidden", "high", "href", "hreflang", "http-equiv", "id",
144+ "inert", "inputmode", "integrity", "ismap", "kind", "label", "lang", "list", "loading",
145+ "loop", "low", "max", "maxlength", "media", "method", "min", "multiple", "muted", "name",
146+ "nomodule", "novalidate", "open", "optimum", "pattern", "placeholder", "playsinline",
147+ "popover", "popovertarget", "popovertargetaction", "poster", "preload", "readonly",
148+ "referrerpolicy", "rel", "required", "reversed", "role", "rows", "rowspan", "sandbox",
149+ "scope", "selected", "size", "sizes", "span", "spellcheck", "src", "srcset", "start",
150+ "step", "tabindex", "target", "title", "translate", "type", "usemap", "value", "width",
151+ "wrap",
152+];
153+
154+/// CSS properties whose bare numbers are lengths, so `margin-top 16` means
155+/// `16px`. (Others, like `opacity` or `z-index`, take unitless numbers.)
156+pub fn is_length_property(name: &str) -> bool {
157+ const PREFIXES: &[&str] = &["margin", "padding", "inset", "scroll-margin", "scroll-padding"];
158+ const SUFFIXES: &[&str] = &["-width", "-height", "-radius", "-offset", "-spacing", "gap"];
159+ const EXACT: &[&str] = &[
160+ "top", "right", "bottom", "left", "width", "height", "font-size", "block-size",
161+ "inline-size", "min-block-size", "max-block-size", "min-inline-size", "max-inline-size",
162+ "text-indent", "flex-basis", "perspective", "text-decoration-thickness",
163+ ];
164+ EXACT.contains(&name)
165+ || PREFIXES.iter().any(|p| name.starts_with(p))
166+ || SUFFIXES.iter().any(|s| name.ends_with(s))
167+}
168+
169+pub fn is_css_property(name: &str) -> bool {
170+ CSS_PROPERTIES.binary_search(&name).is_ok()
171+}
172+
173+/// Emitted as an HTML attribute on any element.
174+pub fn is_html_passthrough(name: &str) -> bool {
175+ HTML_PASSTHROUGH_ATTRS.contains(&name) || name.starts_with("aria-") || name.starts_with("data-")
176+}
177+
178+pub fn is_known_attribute(name: &str) -> bool {
179+ HTMLANG_ATTRIBUTES.contains(&name)
180+ || is_css_property(name)
181+ || HTML_ATTRIBUTES.contains(&name)
182+ || name.starts_with("aria-")
183+ || name.starts_with("data-")
184+}
185+
186+/// All known attribute names, for "did you mean" suggestions.
187+pub fn all_attributes() -> Vec<&'static str> {
188+ HTMLANG_ATTRIBUTES
189+ .iter()
190+ .chain(CSS_PROPERTIES)
191+ .chain(HTML_ATTRIBUTES)
192+ .copied()
193+ .collect()
194+}
195+
196+#[cfg(test)]
197+mod tests {
198+ #[test]
199+ fn css_properties_are_sorted() {
200+ assert!(super::CSS_PROPERTIES.windows(2).all(|w| w[0] < w[1]));
201+ }
202+}