diff --git a/moli-layout/src/builder.rs b/moli-layout/src/builder.rs index db4b60bde7..c590caeadd 100644 --- a/moli-layout/src/builder.rs +++ b/moli-layout/src/builder.rs @@ -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) diff --git a/moli-layout/src/style.rs b/moli-layout/src/style.rs index 60df158c9e..f95a3bb31f 100644 --- a/moli-layout/src/style.rs +++ b/moli-layout/src/style.rs @@ -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( diff --git a/moli-layout/tests/main_layout_contract.rs b/moli-layout/tests/main_layout_contract.rs index a381965bbc..94afac98e2 100644 --- a/moli-layout/tests/main_layout_contract.rs +++ b/moli-layout/tests/main_layout_contract.rs @@ -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 [