on master help sign in
CSS values mean what CSS says
a541580iko
crates/htmlang-core/src/codegen.rs−41
@@ -1700,11 +1700,6 @@ fn attrs_to_css(
1700 },
1700 },
1701
1701
1702 // Typography
1702 // Typography
1703− "line-height" => {
1704− if let Some(v) = val {
1705− push_css(&mut css, "line-height", &css_line_height(v));
1706− }
1707− }
1708 "letter-spacing" => {
1703 "letter-spacing" => {
1709 if let Some(v) = val {
1704 if let Some(v) = val {
1710 push_css(&mut css, "letter-spacing", &css_px(v));
1705 push_css(&mut css, "letter-spacing", &css_px(v));
@@ -1987,23 +1982,6 @@ fn attrs_to_css(
1987 }
1982 }
1988 }
1983 }
1989
1984
1990− "content" => {
1991− if let Some(v) = val {
1992− // Wrap in quotes if not already quoted and not a CSS keyword
1993− if v.starts_with('"')
1994− || v.starts_with('\'')
1995− || v == "none"
1996− || v == "normal"
1997− || v.starts_with("attr(")
1998− || v.starts_with("counter(")
1999− {
2000− push_css(&mut css, "content", v);
2001− } else {
2002− push_css(&mut css, "content", &format!("\"{}\"", v));
2003− }
2004− }
2005− }
2006−
2007 // --- CSS Shorthands ---
1985 // --- CSS Shorthands ---
2008 "line-clamp" => {
1986 "line-clamp" => {
2009 if let Some(v) = val {
1987 if let Some(v) = val {
@@ -2071,25 +2049,6 @@ fn css_px(value: &str) -> String {
2071 }
2049 }
2072}
2050}
2073
2051
2074−/// Format a `line-height` value. CSS accepts either a unitless multiplier
2075−/// (e.g. `1.5`) or a length (e.g. `24px`). Plain integers in htmlang source
2076−/// are ambiguous: `[line-height 24]` was historically emitted as `24` (which
2077−/// CSS interprets as 24× font-size — almost never what anyone wants). Treat
2078−/// integers ≥ 2 as pixel lengths; anything with a decimal, an existing unit,
2079−/// or the value `0`/`1` passes through unchanged.
2080−fn css_line_height(value: &str) -> String {
2081− let v = value.trim();
2082− if v == "0" || v == "1" {
2083− return v.to_string();
2084− }
2085− // Plain integer — treat as pixel length. Decimals, units, keywords and
2086− // functions pass through.
2087− if !v.is_empty() && v.chars().all(|c| c.is_ascii_digit()) {
2088− return format!("{}px", v);
2089− }
2090− v.to_string()
2091−}
2092−
2093/// Format multiple space-separated values, each getting px if needed.
2052/// Format multiple space-separated values, each getting px if needed.
2094fn css_px_multi(value: &str) -> String {
2053fn css_px_multi(value: &str) -> String {
2095 value
2054 value
crates/htmlang-core/src/vocab.rs+5−3
@@ -155,9 +155,11 @@ pub fn is_length_property(name: &str) -> bool {
155 "border", "border-top", "border-right", "border-bottom", "border-left", "border-block",
155 "border", "border-top", "border-right", "border-bottom", "border-left", "border-block",
156 "border-inline", "outline", "column-rule",
156 "border-inline", "outline", "column-rule",
157 ];
157 ];
158+ // A bare `line-height` number is a multiplier, not a length
159+ name != "line-height"
158− EXACT.contains(&name)
160+ && (EXACT.contains(&name)
159− || PREFIXES.iter().any(|p| name.starts_with(p))
161+ || PREFIXES.iter().any(|p| name.starts_with(p))
160− || SUFFIXES.iter().any(|s| name.ends_with(s))
162+ || SUFFIXES.iter().any(|s| name.ends_with(s)))
161}
163}
162
164
163/// State prefixes and the selector each adds: `hover:color red` styles
165/// State prefixes and the selector each adds: `hover:color red` styles
examples/demo.hl+1−1
@@ -163,7 +163,7 @@
163 @el [
163 @el [
164 width 24, height 24, border-radius 4,
164 width 24, height 24, border-radius 4,
165 background $primary, color white,
165 background $primary, color white,
166− text-align center, font-size 12, font-weight bold, line-height 24
166+ text-align center, font-size 12, font-weight bold, line-height 24px
167 ]
167 ]
168 $i
168 $i
169
169
tests/regressions.rs+8
@@ -455,3 +455,11 @@ fn links_and_images_are_output_as_written() {
455 assert!(!out.contains("Skip to content"), "{}", out);
455 assert!(!out.contains("Skip to content"), "{}", out);
456 assert!(!out.contains("hl-main"), "{}", out);
456 assert!(!out.contains("hl-main"), "{}", out);
457}
457}
458+
459+#[test]
460+fn css_values_pass_through() {
461+ let out = compile("@el [line-height 24, before:content \"→ \", after:content attr(title)] x");
462+ assert!(out.contains("line-height:24;"), "{}", out);
463+ assert!(out.contains("content:\"→ \""), "{}", out);
464+ assert!(out.contains("content:attr(title)"), "{}", out);
465+}
tests/snapshots.rs+2−2
@@ -2225,7 +2225,7 @@ fn each_else_non_empty_list() {
2225
2225
2226#[test]
2226#[test]
2227fn pseudo_element_before_content() {
2227fn pseudo_element_before_content() {
2228− let output = compile("@el [before:content arrow, before:color red]\n Hello");
2228+ let output = compile("@el [before:content \"arrow\", before:color red]\n Hello");
2229 assert!(
2229 assert!(
2230 output.contains("::before"),
2230 output.contains("::before"),
2231 "should generate ::before CSS: {}",
2231 "should generate ::before CSS: {}",
@@ -2245,7 +2245,7 @@ fn pseudo_element_before_content() {
2245
2245
2246#[test]
2246#[test]
2247fn pseudo_element_after_content() {
2247fn pseudo_element_after_content() {
2248− let output = compile("@el [after:content ✓]\n Done");
2248+ let output = compile("@el [after:content \"✓\"]\n Done");
2249 assert!(
2249 assert!(
2250 output.contains("::after"),
2250 output.contains("::after"),
2251 "should generate ::after CSS: {}",
2251 "should generate ::after CSS: {}",