Preserve percentage bases in simple float bands
This commit is contained in:
1 parent
b744b9b437
commit
d55e48a10f
2 files changed
+239
No files matched your search
@@ -7914,6 +7914,145 @@ fn build_children_with_float_zone(
|
|||||||
) -> Vec<taffy::NodeId> {
|
) -> Vec<taffy::NodeId> {
|
||||||
let is_float = |cid: NodeId| styles.get(&cid).map(|s| s.float.is_some()).unwrap_or(false);
|
let is_float = |cid: NodeId| styles.get(&cid).map(|s| s.float.is_some()).unwrap_or(false);
|
||||||
|
|
||||||
|
// A definite-height block whose only substantive contents are either one
|
||||||
|
// full-width float or one percentage float followed by one percentage
|
||||||
|
// inline atom has a bounded, single float band. The general legacy path
|
||||||
|
// below cannot represent that shape: it puts the float in an auto-width,
|
||||||
|
// zero-height escape wrapper, so both percentage axes resolve against an
|
||||||
|
// indefinite synthetic box. A Bootstrap `width:100%;height:100%` column
|
||||||
|
// then collapses or moves away from block-start, and the equally common
|
||||||
|
// `[float:left;width:50%][inline-block;width:50%]` control row gives the
|
||||||
|
// float zero size.
|
||||||
|
//
|
||||||
|
// Keep this deliberately narrower than a float manager. In this exact
|
||||||
|
// one-band case a full-size flex row is only a placement representation:
|
||||||
|
// its percentage basis is the real containing block's content box, its
|
||||||
|
// block-start is the normal flow position, and it cannot incorrectly
|
||||||
|
// contain an escaping float because the parent already owns a definite
|
||||||
|
// block size. Text, multiple flow siblings, auto sizes, and floats that
|
||||||
|
// can extend beyond an auto-height parent retain the general path.
|
||||||
|
let parent_has_definite_height = styles.get(&parent_id).is_some_and(|parent| {
|
||||||
|
matches!(
|
||||||
|
parent.height,
|
||||||
|
crate::Dimension::Px(_) | crate::Dimension::Percent(_)
|
||||||
|
) && parent.size_expressions[1].is_none()
|
||||||
|
&& !has_in_flow_generated_pseudo(parent.before_pseudo.as_deref())
|
||||||
|
&& !has_in_flow_generated_pseudo(parent.after_pseudo.as_deref())
|
||||||
|
});
|
||||||
|
if parent_has_definite_height {
|
||||||
|
let fills_axis = |value: f32| (value - 1.0).abs() < 0.001;
|
||||||
|
let substantive: Vec<NodeId> = dom_children
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.filter(|cid| {
|
||||||
|
tree.get_node(*cid).is_some_and(|node| {
|
||||||
|
node.is_element() || !tree.text_content(*cid).trim().is_empty()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let floated: Vec<NodeId> = substantive
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.filter(|cid| is_float(*cid))
|
||||||
|
.collect();
|
||||||
|
if floated.len() == 1 {
|
||||||
|
let float_dom = floated[0];
|
||||||
|
let float_style = styles.get(&float_dom);
|
||||||
|
let float_percent_width = float_style.and_then(|style| match style.width {
|
||||||
|
crate::Dimension::Percent(value) => Some(value),
|
||||||
|
_ => None,
|
||||||
|
});
|
||||||
|
let float_fills_height = float_style.is_some_and(|style| {
|
||||||
|
matches!(style.height, crate::Dimension::Percent(value) if fills_axis(value))
|
||||||
|
&& style.size_expressions[1].is_none()
|
||||||
|
});
|
||||||
|
let sole_full_width_float = substantive.len() == 1
|
||||||
|
&& float_percent_width.is_some_and(fills_axis)
|
||||||
|
&& float_fills_height;
|
||||||
|
let split_band_flow = if substantive.len() == 2
|
||||||
|
&& substantive[0] == float_dom
|
||||||
|
&& float_style.and_then(|style| style.float) == Some(crate::Float::Left)
|
||||||
|
&& float_fills_height
|
||||||
|
{
|
||||||
|
let flow_dom = substantive[1];
|
||||||
|
styles.get(&flow_dom).and_then(|flow_style| {
|
||||||
|
let flow_percent_width = match flow_style.width {
|
||||||
|
crate::Dimension::Percent(value) => value,
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
let flow_fills_height = matches!(
|
||||||
|
flow_style.height,
|
||||||
|
crate::Dimension::Percent(value) if fills_axis(value)
|
||||||
|
) && flow_style.size_expressions[1].is_none();
|
||||||
|
let widths_fill_one_band = float_percent_width.is_some_and(|float_width| {
|
||||||
|
float_width > 0.0
|
||||||
|
&& flow_percent_width > 0.0
|
||||||
|
&& (float_width + flow_percent_width - 1.0).abs() < 0.001
|
||||||
|
});
|
||||||
|
(flow_style.display != crate::Display::None
|
||||||
|
&& !flow_style.display_contents
|
||||||
|
&& flow_style.is_inline_block
|
||||||
|
&& flow_fills_height
|
||||||
|
&& widths_fill_one_band)
|
||||||
|
.then_some(flow_dom)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
if sole_full_width_float || split_band_flow.is_some() {
|
||||||
|
let float_node = build(
|
||||||
|
tree,
|
||||||
|
float_dom,
|
||||||
|
taffy_tree,
|
||||||
|
id_map,
|
||||||
|
words,
|
||||||
|
engine,
|
||||||
|
ifc_items,
|
||||||
|
styles,
|
||||||
|
);
|
||||||
|
// The split-band guard admits only a boxed inline-block
|
||||||
|
// element, so it must be built as one atomic box. Calling
|
||||||
|
// `build_any` here would be needlessly fragile: its text and
|
||||||
|
// transparent-inline branches may fan out into several nodes,
|
||||||
|
// and discovering that only after construction would leave
|
||||||
|
// detached nodes behind when falling back to the general path.
|
||||||
|
let flow_node = split_band_flow.and_then(|flow_dom| {
|
||||||
|
build(
|
||||||
|
tree,
|
||||||
|
flow_dom,
|
||||||
|
taffy_tree,
|
||||||
|
id_map,
|
||||||
|
words,
|
||||||
|
engine,
|
||||||
|
ifc_items,
|
||||||
|
styles,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
if let Some(float_node) = float_node {
|
||||||
|
if sole_full_width_float || flow_node.is_some() {
|
||||||
|
let children: Vec<taffy::NodeId> =
|
||||||
|
[Some(float_node), flow_node].into_iter().flatten().collect();
|
||||||
|
let band_style = taffy::Style {
|
||||||
|
display: taffy::style::Display::Flex,
|
||||||
|
flex_direction: taffy::FlexDirection::Row,
|
||||||
|
flex_wrap: taffy::FlexWrap::NoWrap,
|
||||||
|
align_items: Some(taffy::AlignItems::FLEX_START),
|
||||||
|
size: taffy::Size {
|
||||||
|
width: taffy::Dimension::percent(1.0),
|
||||||
|
height: taffy::Dimension::percent(1.0),
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
if let Ok(band) = taffy_tree.new_with_children(band_style, &children) {
|
||||||
|
return vec![band];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// A block made entirely from inline-ish flow content and two or more
|
// A block made entirely from inline-ish flow content and two or more
|
||||||
// right floats is the classic utility/navigation bar. Right floats are
|
// right floats is the classic utility/navigation bar. Right floats are
|
||||||
// placed from the inline end inward, so their visual order is the reverse
|
// placed from the inline end inward, so their visual order is the reverse
|
||||||
|
|||||||
@@ -885,6 +885,106 @@ fn float_exclusion_continues_through_non_bfc_block_wrappers() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn full_width_percentage_float_uses_definite_containing_block() {
|
||||||
|
let tree = parse_html(
|
||||||
|
r#"
|
||||||
|
<style>
|
||||||
|
html, body { margin: 0; width: 100%; height: 100% }
|
||||||
|
#host { width: 100%; height: 100% }
|
||||||
|
#float {
|
||||||
|
float: left;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
#badges { display: flex; gap: 20px }
|
||||||
|
.badge { width: 100px; height: 48px }
|
||||||
|
</style>
|
||||||
|
<div id="host">
|
||||||
|
<div id="float">
|
||||||
|
<div id="badges">
|
||||||
|
<div class="badge"></div>
|
||||||
|
<div class="badge"></div>
|
||||||
|
<div class="badge"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
let layout = layout_dom(&tree, (800.0, 600.0));
|
||||||
|
let rect = |id| layout.rects[&tree.get_element_by_id(id).unwrap()];
|
||||||
|
let host = rect("host");
|
||||||
|
let float = rect("float");
|
||||||
|
let badges = rect("badges");
|
||||||
|
assert!(
|
||||||
|
(host.x - 0.0).abs() < 0.01
|
||||||
|
&& (host.y - 0.0).abs() < 0.01
|
||||||
|
&& (host.width - 800.0).abs() < 0.01
|
||||||
|
&& (host.height - 600.0).abs() < 0.01,
|
||||||
|
"host: {host:?}"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
(float.x - 0.0).abs() < 0.01
|
||||||
|
&& (float.y - 0.0).abs() < 0.01
|
||||||
|
&& (float.width - 800.0).abs() < 0.01
|
||||||
|
&& (float.height - 600.0).abs() < 0.01,
|
||||||
|
"float: {float:?}"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
(badges.x - 230.0).abs() < 0.01
|
||||||
|
&& (badges.y - 0.0).abs() < 0.01
|
||||||
|
&& (badges.width - 340.0).abs() < 0.01
|
||||||
|
&& (badges.height - 48.0).abs() < 0.01,
|
||||||
|
"nested centered badges: {badges:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn percentage_float_and_inline_sibling_share_definite_height_band() {
|
||||||
|
let tree = parse_html(
|
||||||
|
r#"
|
||||||
|
<style>
|
||||||
|
html, body { margin: 0 }
|
||||||
|
#host {
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 400px;
|
||||||
|
height: 46px;
|
||||||
|
border: 1px solid;
|
||||||
|
}
|
||||||
|
#float, #flow {
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: inline-block;
|
||||||
|
width: 50%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
#float { float: left }
|
||||||
|
</style>
|
||||||
|
<div id="host"><button id="float"></button><button id="flow"></button></div>
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
let layout = layout_dom(&tree, (800.0, 200.0));
|
||||||
|
let rect = |id| layout.rects[&tree.get_element_by_id(id).unwrap()];
|
||||||
|
let float = rect("float");
|
||||||
|
let flow = rect("flow");
|
||||||
|
assert!(
|
||||||
|
(float.x - 1.0).abs() < 0.01
|
||||||
|
&& (float.y - 1.0).abs() < 0.01
|
||||||
|
&& (float.width - 199.0).abs() < 0.01
|
||||||
|
&& (float.height - 44.0).abs() < 0.01,
|
||||||
|
"float: {float:?}"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
(flow.x - 200.0).abs() < 0.01
|
||||||
|
&& (flow.y - 1.0).abs() < 0.01
|
||||||
|
&& (flow.width - 199.0).abs() < 0.01
|
||||||
|
&& (flow.height - 44.0).abs() < 0.01,
|
||||||
|
"flow: {flow:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn replaced_image_contributes_intrinsic_size_in_ordered_grid() {
|
fn replaced_image_contributes_intrinsic_size_in_ordered_grid() {
|
||||||
let tree = parse_html(include_str!("../../../render-repros/replaced-grid-order.html"));
|
let tree = parse_html(include_str!("../../../render-repros/replaced-grid-order.html"));
|
||||||
|
|||||||
Reference in new issue
Block a user