render: preserve framework inline control rows

This commit is contained in:
SGavrl committed 2026-07-26 12:06:26 +00:00
1 parent 3618549842
commit 5a89ebd0a3
2 files changed
+71 -22

No files matched your search

+40 -22
View File
@@ -3153,6 +3153,26 @@ fn build(
if flex_or_grid_item { if flex_or_grid_item {
taffy_style.display = taffy::style::Display::Block; taffy_style.display = taffy::style::Display::Block;
taffy_style.flex_wrap = taffy::FlexWrap::NoWrap; taffy_style.flex_wrap = taffy::FlexWrap::NoWrap;
} else if style.width == crate::Dimension::Auto {
// An auto-width inline-block shrink-fits to its max-content width
// when that fits the available line. Taffy's wrapping flex
// approximation otherwise chooses a min-content width first,
// turning ordinary two-word buttons into two-line controls. Keep
// wrapping for explicitly constrained inline-blocks and for ones
// with real in-flow block children; those need their inner block
// formatting rather than a single max-content line.
let has_in_flow_block_child = tree.children(id).iter().any(|child| {
styles.get(child).map_or(false, |child_style| {
child_style.display == crate::Display::Block
&& !matches!(
child_style.position,
Some(taffy::Position::Absolute)
)
})
});
if !has_in_flow_block_child {
taffy_style.flex_wrap = taffy::FlexWrap::NoWrap;
}
} }
} }
@@ -3299,19 +3319,6 @@ fn build(
&& !matches!(child.position, Some(taffy::Position::Absolute)) && !matches!(child.position, Some(taffy::Position::Absolute))
}) })
}); });
let has_replaced_block_child = style.display == crate::Display::Block
&& dom_children.iter().any(|cid| {
let replaced_wrapper = tree
.get_node(*cid)
.and_then(|node| node.as_element().map(|name| matches!(name.local.as_ref(), "picture" | "img")))
.unwrap_or(false);
replaced_wrapper
&& styles.get(cid).map_or(false, |child| {
child.display == crate::Display::Block
&& child.float.is_none()
&& !matches!(child.position, Some(taffy::Position::Absolute))
})
});
let is_native_table_cell = node.as_element().map_or(false, |element| { let is_native_table_cell = node.as_element().map_or(false, |element| {
matches!(element.local.as_ref(), "td" | "th") matches!(element.local.as_ref(), "td" | "th")
&& style.internal_flex_container && style.internal_flex_container
@@ -3319,15 +3326,18 @@ fn build(
// `text-align` affects inline content, never the used width or placement // `text-align` affects inline content, never the used width or placement
// of an in-flow block child. `to_taffy_style` promotes a centered/right // of an in-flow block child. `to_taffy_style` promotes a centered/right
// block to a flex column as an inline-alignment stand-in, but retaining // block to a flex column as an inline-alignment stand-in; retaining that
// that promotion for a block-level replaced wrapper makes its auto width // promotion when the container has real block children makes every auto
// shrink to max-content. A responsive `<picture><img width:100%>` then // width child shrink to max-content. That turns full-width paragraphs,
// overflows a padded figure at its intrinsic width instead of filling the // responsive picture wrappers, and rows of inline-block buttons into
// figure's content box. Preserve block formatting for that concrete // narrow columns that wrap even though their containing block has room.
// modern-media shape. Mixed inline/block content is handled separately by //
// `build_mixed_block` below; keeping this guard narrow avoids changing the // Keep the legacy `<center>` behavior: unlike CSS text-align, that element
// legacy shrink-wrap approximation for unrelated centered block children. // historically centers block descendants as well.
if has_replaced_block_child { if style.display == crate::Display::Block
&& has_in_flow_block_child
&& !style.legacy_center
{
taffy_style.display = taffy::style::Display::Block; taffy_style.display = taffy::style::Display::Block;
} }
@@ -3511,6 +3521,14 @@ fn build_mixed_block(
for &cid in &flat { for &cid in &flat {
let Some(node) = tree.get_node(cid) else { continue }; let Some(node) = tree.get_node(cid) else { continue };
let is_text = matches!(node.data, obscura_dom::tree::NodeData::Text { .. }); let is_text = matches!(node.data, obscura_dom::tree::NodeData::Text { .. });
// Comments, doctypes, and other non-rendered DOM nodes generate no CSS
// box and cannot interrupt an inline formatting context. Hydrating
// frameworks place marker comments between adjacent inline controls;
// treating each marker as a block segment split one button row into
// multiple anonymous block wrappers.
if !is_text && !node.is_element() {
continue;
}
let inline_level = if is_text { let inline_level = if is_text {
true true
} else if let Some(s) = styles.get(&cid) { } else if let Some(s) = styles.get(&cid) {
@@ -659,6 +659,37 @@ fn text_alignment_does_not_shrink_flex_items() {
); );
} }
#[test]
fn text_alignment_does_not_shrink_block_children_or_wrap_inline_block_rows() {
let tree = parse_html(
r#"<html><head><style>
html,body{margin:0}
section{width:900px;text-align:center}
p{margin:0}
a{display:inline-block;width:100px;height:40px;margin-right:10px}
</style></head><body><section>
<p id="row"><!--[--><a id="one">Why Vue</a><!--]--><a id="two"></a><!--marker--><a id="three"></a><a id="four"></a></p>
</section></body></html>"#,
);
let layout = layout_dom(&tree, (900.0, 1000.0));
let rect = |id| layout.rects[&tree.get_element_by_id(id).unwrap()];
let row = rect("row");
let items = [rect("one"), rect("two"), rect("three"), rect("four")];
assert!(
(row.width - 900.0).abs() < 0.01,
"text-align must not shrink-wrap an auto-width block child: {row:?}"
);
assert!(
items.windows(2).all(|pair| (pair[0].y - pair[1].y).abs() < 0.01),
"inline-block items should share one line when the block has room: {items:?}"
);
assert!(
items[0].height <= 40.01,
"auto-width inline-block text should use one max-content line: {:?}",
items[0]
);
}
#[test] #[test]
fn inline_block_flex_items_keep_block_inner_flow() { fn inline_block_flex_items_keep_block_inner_flow() {
let tree = parse_html(include_str!("../../../render-repros/inline-block-flex-items.html")); let tree = parse_html(include_str!("../../../render-repros/inline-block-flex-items.html"));