diff --git a/crates/obscura-render/src/style.rs b/crates/obscura-render/src/style.rs index e23ed72..e527e91 100644 --- a/crates/obscura-render/src/style.rs +++ b/crates/obscura-render/src/style.rs @@ -2795,8 +2795,25 @@ fn set_grid_placement_side( /// alphabetic token that is not a bare `span ` count), so it must defer to /// the parent's line-name map. fn grid_line_has_name(value: &str) -> bool { + let value = value.trim(); + // `auto` is a grid-placement keyword, not a custom-ident. Sending + // `auto / span 4` down the named-line path loses the span whenever the + // parent has no named-line map. CSS-wide keywords likewise cannot name a + // line and must retain their reset-to-auto behavior in this compact + // computed-style model. + if value.eq_ignore_ascii_case("auto") + || matches!( + value.to_ascii_lowercase().as_str(), + "initial" | "inherit" | "unset" | "revert" | "revert-layer" + ) + { + return false; + } value.split('/').any(|part| { let p = part.trim(); + if p.eq_ignore_ascii_case("auto") { + return false; + } let rest = p.strip_prefix("span").map(str::trim).unwrap_or(p); rest.chars().any(|c| c.is_ascii_alphabetic()) }) @@ -5653,6 +5670,39 @@ mod tests { ); } + #[test] + fn grid_placement_shorthand_keeps_auto_span_out_of_named_line_resolution() { + let style = compute_style("div", Some("grid-column:auto / span 4")); + assert_eq!( + style.grid_column, + Some(taffy::Line { + start: taffy::GridPlacement::Auto, + end: taffy::style_helpers::span(4), + }) + ); + assert_eq!(style.grid_column_raw, None); + + let named = compute_style("div", Some("grid-column:content-start / content-end")); + assert_eq!( + named.grid_column_raw.as_deref(), + Some("content-start / content-end") + ); + assert_eq!(named.grid_column, None); + + let reset = compute_style( + "div", + Some("grid-column:2 / span 4;grid-column:initial"), + ); + assert_eq!( + reset.grid_column, + Some(taffy::Line { + start: taffy::GridPlacement::Auto, + end: taffy::GridPlacement::Auto, + }) + ); + assert_eq!(reset.grid_column_raw, None); + } + #[test] fn contextual_css_math_uses_runtime_geometry() { let context = (20.0, 16.0, 9.0, 10.0, 900.0); diff --git a/crates/obscura-render/tests/layout_test.rs b/crates/obscura-render/tests/layout_test.rs index 86d42aa..473718f 100644 --- a/crates/obscura-render/tests/layout_test.rs +++ b/crates/obscura-render/tests/layout_test.rs @@ -1636,6 +1636,66 @@ fn responsive_grid_child_spans_all_tracks_via_end_longhand() { ); } +#[test] +fn grid_column_auto_span_shorthand_places_six_cards_in_two_rows() { + let tree = parse_html( + r#" + + + "#, + ); + let layout = layout_dom(&tree, (1440.0, 900.0)); + let cards: Vec<_> = (1..=6) + .map(|index| { + let id = tree + .get_element_by_id(&format!("card-{index}")) + .expect("card exists"); + layout.rects[&id] + }) + .collect(); + + for card in &cards { + assert!( + (card.width - 448.0).abs() < 0.01, + "four of twelve tracks plus internal gaps should be 448px: {card:?}" + ); + } + assert_eq!(cards[0].y, cards[1].y); + assert_eq!(cards[1].y, cards[2].y); + assert_eq!(cards[3].y, cards[4].y); + assert_eq!(cards[4].y, cards[5].y); + assert!( + (cards[3].y - cards[0].y - 164.0).abs() < 0.01, + "fourth card should start a second row after the 64px row gap: {:?}", + cards + ); + assert!( + (cards[1].x - cards[0].x - 464.0).abs() < 0.01 + && (cards[2].x - cards[1].x - 464.0).abs() < 0.01, + "cards should advance by four tracks plus the outer column gap: {:?}", + cards + ); +} + #[test] fn length_line_height_computes_before_inheritance() { let tree = parse_html(