fix(layout): retain normal grid stretching for native form controls

Map native controls to compressible replaced leaves without the image-only replaced flag. Cover checkbox, radio and range normal alignment at 50 by 50 pixels.

Validation: cargo fmt --all; cargo clippy --workspace --all-targets --all-features -- -D warnings; cargo nextest run --no-fail-fast (20283 passed, 18 skipped). Release v3 and five browser regression rounds. Full WPT recorded separately.
This commit is contained in:
ldm0
2026-10-19 04:52:47 +08:00
parent 0232344406
commit 143e72ec4d
3 changed files with 58 additions and 4 deletions
+6 -2
View File
@@ -138,7 +138,9 @@ where
root_style.force_display_none();
}
if root_semantics.is_replaced() {
root_style.mark_replaced();
root_style.mark_replaced(
root_semantics.replaced == Some(crate::LayoutReplacedKind::FormControl),
);
} else if matches!(
root_semantics.category,
crate::LayoutElementCategory::FormControl(crate::LayoutFormControlKind::Button)
@@ -306,7 +308,9 @@ where
}
let metrics = self.source.replaced_metrics(source_node);
if semantics.is_replaced() {
style.mark_replaced();
style.mark_replaced(
semantics.replaced == Some(crate::LayoutReplacedKind::FormControl),
);
} else if matches!(
semantics.category,
crate::LayoutElementCategory::FormControl(crate::LayoutFormControlKind::Button)
+5 -2
View File
@@ -2121,8 +2121,11 @@ impl ResolvedLayoutStyle {
}
}
pub(crate) fn mark_replaced(&mut self) {
self.taffy.item_is_replaced = true;
pub(crate) fn mark_replaced(&mut self, is_form_control: bool) {
// Taffy distinguishes native controls from images for normal grid/OOF
// stretch. Both remain compressible replaced leaves in block layout.
self.taffy.item_is_replaced = !is_form_control;
self.taffy.item_is_compressible_replaced = is_form_control;
}
pub(crate) fn resolved_aspect_ratio(
+47
View File
@@ -709,6 +709,53 @@ fn grid_item_placement_and_self_alignment_use_the_selected_grid_area() {
);
}
#[test]
fn native_form_controls_stretch_under_normal_grid_alignment() {
use moli_layout::{LayoutFormControlKind, LayoutInputControlKind};
for kind in [
LayoutInputControlKind::Checkbox,
LayoutInputControlKind::Radio,
LayoutInputControlKind::Range,
] {
let mut control = FixtureNode::html("control", "input", Vec::new());
control.semantics = LayoutElementSemantics::new(
LayoutNamespace::Html,
"input",
LayoutElementCategory::FormControl(LayoutFormControlKind::Input(kind)),
Some(LayoutReplacedKind::FormControl),
);
let source = FixtureSource {
nodes: vec![FixtureNode::div("grid", vec![1]), control],
};
let mut styles = FixtureStyles::default();
styles.0.insert(
0,
resolved(
LayoutDisplay::Grid,
Style {
display: Display::Grid,
size: Size {
width: length(50.0),
height: length(50.0),
},
grid_template_columns: vec![length(50.0)],
grid_template_rows: vec![length(50.0)],
..Style::default()
},
PaintColor::TRANSPARENT,
),
);
styles
.0
.insert(1, resolved(LayoutDisplay::Block, Style::default(), RED));
let snapshot = render(&source, &mut styles, PaintViewport::new(100, 100, 1.0));
assert_rect(
solid_rect(&snapshot, RED),
PaintRect::new(0.0, 0.0, 50.0, 50.0),
);
}
}
#[test]
fn absolute_grid_items_use_retained_track_areas_in_both_directions() {
for (direction, x) in [