Support implicit grid track sizing

This commit is contained in:
SGavrl committed 2026-07-31 11:14:35 +00:00
1 parent 65ed38a71d
commit 7a1dc43d3c
3 files changed
+306

No files matched your search

+145
View File
@@ -2233,7 +2233,36 @@ fn layout_dom_once(
// (updated to its content width inside the block below). // (updated to its content width inside the block below).
let mut child_cb_width = inh.cb_width; let mut child_cb_width = inh.cb_width;
let mut child_cb_height_definite = false; let mut child_cb_height_definite = false;
let inherited_grid_auto_tracks = styles.get(&id).and_then(|style| {
(style.grid_auto_columns_inherit || style.grid_auto_rows_inherit)
.then(|| {
tree.get_node(id)
.and_then(|node| node.parent)
.and_then(|parent| styles.get(&parent))
.map(|parent| {
(
parent.grid_auto_columns.clone(),
parent.grid_auto_rows.clone(),
)
})
.unwrap_or_default()
})
});
if let Some(style) = styles.get_mut(&id) { if let Some(style) = styles.get_mut(&id) {
if style.grid_auto_columns_inherit {
style.grid_auto_columns = inherited_grid_auto_tracks
.as_ref()
.map(|tracks| tracks.0.clone())
.unwrap_or_default();
style.grid_auto_columns_inherit = false;
}
if style.grid_auto_rows_inherit {
style.grid_auto_rows = inherited_grid_auto_tracks
.as_ref()
.map(|tracks| tracks.1.clone())
.unwrap_or_default();
style.grid_auto_rows_inherit = false;
}
if style.display_inherit { if style.display_inherit {
style.display = inh.display; style.display = inh.display;
style.display_contents = inh.display_contents; style.display_contents = inh.display_contents;
@@ -2690,6 +2719,8 @@ fn layout_dom_once(
let host_is_inline_block = style.is_inline_block; let host_is_inline_block = style.is_inline_block;
let host_flow_root = style.flow_root; let host_flow_root = style.flow_root;
let host_is_table_box = style.is_table_box; let host_is_table_box = style.is_table_box;
let host_grid_auto_columns = style.grid_auto_columns.clone();
let host_grid_auto_rows = style.grid_auto_rows.clone();
let host_overflow_x = if style.overflow_scroll_x { let host_overflow_x = if style.overflow_scroll_x {
2 2
} else if style.overflow_clip_x { } else if style.overflow_clip_x {
@@ -2705,6 +2736,14 @@ fn layout_dom_once(
0 0
}; };
let settle_pseudo = |pseudo: &mut crate::LayoutStyle| { let settle_pseudo = |pseudo: &mut crate::LayoutStyle| {
if pseudo.grid_auto_columns_inherit {
pseudo.grid_auto_columns = host_grid_auto_columns.clone();
pseudo.grid_auto_columns_inherit = false;
}
if pseudo.grid_auto_rows_inherit {
pseudo.grid_auto_rows = host_grid_auto_rows.clone();
pseudo.grid_auto_rows_inherit = false;
}
if pseudo.display_inherit { if pseudo.display_inherit {
pseudo.display = host_display; pseudo.display = host_display;
pseudo.display_contents = host_display_contents; pseudo.display_contents = host_display_contents;
@@ -11615,4 +11654,110 @@ mod tests {
); );
} }
#[test]
fn grid_auto_columns_size_successive_implicit_column_tracks() {
let tree = parse_html(
r#"<style>
html,body { margin:0 }
#grid {
display:grid;
width:300px;
grid-template-columns:100px;
grid-auto-columns:50px;
grid-auto-flow:column;
}
.item { height:20px }
</style>
<div id="grid">
<div id="first" class="item"></div>
<div id="second" class="item"></div>
<div id="third" class="item"></div>
</div>"#,
);
let laid = layout_dom(&tree, (500.0, 200.0));
let rect = |id| laid.rects[&tree.get_element_by_id(id).unwrap()];
assert_eq!(
rect("first"),
Rect { x: 0.0, y: 0.0, width: 100.0, height: 20.0 }
);
assert_eq!(
rect("second"),
Rect { x: 100.0, y: 0.0, width: 50.0, height: 20.0 }
);
assert_eq!(
rect("third"),
Rect { x: 150.0, y: 0.0, width: 50.0, height: 20.0 }
);
}
#[test]
fn grid_auto_rows_size_successive_implicit_row_tracks() {
let tree = parse_html(
r#"<style>
html,body { margin:0 }
#grid {
display:grid;
width:100px;
height:300px;
grid-template-columns:100px;
grid-template-rows:100px;
grid-auto-rows:50px;
grid-auto-flow:row;
}
</style>
<div id="grid">
<div id="first"></div>
<div id="second"></div>
<div id="third"></div>
</div>"#,
);
let laid = layout_dom(&tree, (500.0, 400.0));
let rect = |id| laid.rects[&tree.get_element_by_id(id).unwrap()];
assert_eq!(
rect("first"),
Rect { x: 0.0, y: 0.0, width: 100.0, height: 100.0 }
);
assert_eq!(
rect("second"),
Rect { x: 0.0, y: 100.0, width: 100.0, height: 50.0 }
);
assert_eq!(
rect("third"),
Rect { x: 0.0, y: 150.0, width: 100.0, height: 50.0 }
);
}
#[test]
fn grid_auto_track_inherit_uses_the_parent_computed_lists() {
let tree = parse_html(
r#"<style>
html,body { margin:0 }
#parent { grid-auto-columns:60px;grid-auto-rows:70px }
#grid {
display:grid;
grid-template-columns:100px;
grid-auto-columns:inherit;
grid-auto-rows:inherit;
grid-auto-flow:column;
}
#grid > div { height:20px }
</style>
<div id="parent">
<div id="grid"><div></div><div id="implicit"></div></div>
</div>"#,
);
let laid = layout_dom(&tree, (500.0, 300.0));
let grid = tree.get_element_by_id("grid").unwrap();
let implicit = tree.get_element_by_id("implicit").unwrap();
assert_eq!(laid.rects[&implicit].x, 100.0);
assert_eq!(laid.rects[&implicit].width, 60.0);
assert_eq!(laid.styles[&grid].grid_auto_columns.len(), 1);
assert_eq!(laid.styles[&grid].grid_auto_rows.len(), 1);
assert!(!laid.styles[&grid].grid_auto_columns_inherit);
assert!(!laid.styles[&grid].grid_auto_rows_inherit);
}
} }
+16
View File
@@ -733,6 +733,16 @@ pub struct LayoutStyle {
// resolved to line placements on children in a later pass. // resolved to line placements on children in a later pass.
pub grid_template_columns: Vec<taffy::GridTemplateComponent<String>>, pub grid_template_columns: Vec<taffy::GridTemplateComponent<String>>,
pub grid_template_rows: Vec<taffy::GridTemplateComponent<String>>, pub grid_template_rows: Vec<taffy::GridTemplateComponent<String>>,
/// Track sizing functions for columns created outside the explicit grid.
/// An empty list is the CSS initial `auto` value: taffy supplies one
/// automatic implicit track and cycles a non-empty authored list.
pub grid_auto_columns: Vec<taffy::TrackSizingFunction>,
/// Track sizing functions for rows created outside the explicit grid.
pub grid_auto_rows: Vec<taffy::TrackSizingFunction>,
/// Explicit CSS-wide `inherit` markers. The properties are normally
/// non-inherited, so only the keyword copies the parent's computed list.
pub(crate) grid_auto_columns_inherit: bool,
pub(crate) grid_auto_rows_inherit: bool,
/// `grid-template-columns: subgrid`. Taffy has no native subgrid track /// `grid-template-columns: subgrid`. Taffy has no native subgrid track
/// component, so `dom` resolves the safe full-span column subset after /// component, so `dom` resolves the safe full-span column subset after
/// measuring the non-subgridded ancestor's intrinsic track contributions. /// measuring the non-subgridded ancestor's intrinsic track contributions.
@@ -1457,6 +1467,12 @@ pub(crate) fn to_taffy_style(style: &LayoutStyle) -> Style {
if !style.grid_template_rows.is_empty() { if !style.grid_template_rows.is_empty() {
s.grid_template_rows = style.grid_template_rows.clone(); s.grid_template_rows = style.grid_template_rows.clone();
} }
if !style.grid_auto_columns.is_empty() {
s.grid_auto_columns = style.grid_auto_columns.clone();
}
if !style.grid_auto_rows.is_empty() {
s.grid_auto_rows = style.grid_auto_rows.clone();
}
if let Some(flow) = style.grid_auto_flow { if let Some(flow) = style.grid_auto_flow {
s.grid_auto_flow = flow; s.grid_auto_flow = flow;
} }
+145
View File
@@ -1552,6 +1552,8 @@ fn apply_value(style: &mut LayoutStyle, name: &str, value: &str) {
style.grid_template_rows = tracks; style.grid_template_rows = tracks;
style.grid_row_line_names = (!names.is_empty()).then(|| build_line_map(names)); style.grid_row_line_names = (!names.is_empty()).then(|| build_line_map(names));
} }
"grid-auto-columns" => apply_grid_auto_tracks(style, value, true),
"grid-auto-rows" => apply_grid_auto_tracks(style, value, false),
"grid-template-areas" => style.grid_areas = Some(parse_grid_areas(value)), "grid-template-areas" => style.grid_areas = Some(parse_grid_areas(value)),
"grid-template" => parse_grid_template(style, value), "grid-template" => parse_grid_template(style, value),
"grid" => parse_grid_shorthand(style, value), "grid" => parse_grid_shorthand(style, value),
@@ -1786,6 +1788,8 @@ pub(crate) fn supports_declaration(name: &str, value: &str) -> bool {
| "border-collapse" | "border-collapse"
| "grid-template-columns" | "grid-template-columns"
| "grid-template-rows" | "grid-template-rows"
| "grid-auto-columns"
| "grid-auto-rows"
| "grid-template-areas" | "grid-template-areas"
| "grid-template" | "grid-template"
| "grid" | "grid"
@@ -1901,6 +1905,9 @@ pub(crate) fn supports_declaration(name: &str, value: &str) -> bool {
!part.is_empty() && parse_grid_line_kind(part).is_some() !part.is_empty() && parse_grid_line_kind(part).is_some()
}) })
} }
"grid-auto-columns" | "grid-auto-rows" => {
parse_grid_auto_track_list(value).is_some()
}
"color" | "-webkit-text-fill-color" | "background-color" | "border-color" => { "color" | "-webkit-text-fill-color" | "background-color" | "border-color" => {
parse_color(value).is_some() parse_color(value).is_some()
} }
@@ -2764,6 +2771,105 @@ fn track(tok: &str) -> taffy::TrackSizingFunction {
MinMax { min: min_track(t), max: max_track(t) } MinMax { min: min_track(t), max: max_track(t) }
} }
/// Parse the `<track-size>+` grammar shared by `grid-auto-columns` and
/// `grid-auto-rows`. Unlike template track lists, implicit track lists do not
/// accept line names or `repeat()`: the authored list itself is repeated as
/// needed for successive implicit tracks.
fn parse_grid_auto_track_list(value: &str) -> Option<Vec<taffy::TrackSizingFunction>> {
let tokens = tokenize_tracks(value);
if tokens.is_empty()
|| tokens.iter().any(|token| {
let lower = token.trim().to_ascii_lowercase();
lower.starts_with('[')
|| lower.starts_with("repeat(")
|| lower == "subgrid"
|| matches!(
lower.as_str(),
"initial" | "inherit" | "unset" | "revert" | "revert-layer"
)
|| !valid_grid_auto_track(&lower)
})
{
return None;
}
Some(tokens.iter().map(|token| track(token)).collect())
}
fn valid_grid_auto_track(value: &str) -> bool {
if matches!(value, "auto" | "min-content" | "max-content") {
return true;
}
if let Some(inner) = value
.strip_prefix("minmax(")
.and_then(|inner| inner.strip_suffix(')'))
{
let Some((min, max)) = inner.split_once(',') else {
return false;
};
return valid_grid_track_breadth(min.trim(), false)
&& valid_grid_track_breadth(max.trim(), true);
}
if let Some(inner) = value
.strip_prefix("fit-content(")
.and_then(|inner| inner.strip_suffix(')'))
{
return valid_grid_track_length_percentage(inner.trim());
}
valid_grid_track_breadth(value, true)
}
fn valid_grid_track_breadth(value: &str, flex_allowed: bool) -> bool {
matches!(value, "auto" | "min-content" | "max-content")
|| (flex_allowed
&& value
.strip_suffix("fr")
.and_then(|number| number.trim().parse::<f32>().ok())
.is_some_and(|number| number.is_finite() && number >= 0.0))
|| valid_grid_track_length_percentage(value)
}
fn valid_grid_track_length_percentage(value: &str) -> bool {
let lower = value.to_ascii_lowercase();
if lower == "0" {
return true;
}
for suffix in ["rem", "vmin", "vmax", "px", "pt", "em", "ex", "vw", "vh", "%"] {
if let Some(number) = lower.strip_suffix(suffix) {
return number
.trim()
.parse::<f32>()
.ok()
.is_some_and(|number| number.is_finite() && number >= 0.0);
}
}
false
}
fn apply_grid_auto_tracks(style: &mut LayoutStyle, value: &str, columns: bool) {
let lower = value.trim().to_ascii_lowercase();
let (tracks, inherit) = match lower.as_str() {
"inherit" => (Vec::new(), true),
// These properties are non-inherited. The compact cascade has no
// retained lower-origin/layer declaration for revert, so it follows
// the existing non-inherited-property policy and uses the initial
// automatic implicit track.
"initial" | "unset" | "revert" | "revert-layer" => (Vec::new(), false),
_ => {
let Some(tracks) = parse_grid_auto_track_list(value) else {
return;
};
(tracks, false)
}
};
if columns {
style.grid_auto_columns = tracks;
style.grid_auto_columns_inherit = inherit;
} else {
style.grid_auto_rows = tracks;
style.grid_auto_rows_inherit = inherit;
}
}
fn min_track(tok: &str) -> taffy::MinTrackSizingFunction { fn min_track(tok: &str) -> taffy::MinTrackSizingFunction {
use taffy::MinTrackSizingFunction as M; use taffy::MinTrackSizingFunction as M;
let lower = tok.to_ascii_lowercase(); let lower = tok.to_ascii_lowercase();
@@ -6604,6 +6710,45 @@ mod tests {
assert_eq!(shorthand.grid_template_columns.len(), 3); assert_eq!(shorthand.grid_template_columns.len(), 3);
} }
#[test]
fn implicit_grid_track_lists_parse_and_css_wide_values_reset_them() {
let tracks = compute_style(
"div",
Some(
"grid-auto-columns:50px minmax(20px,1fr);\
grid-auto-rows:min-content 25%;",
),
);
assert_eq!(tracks.grid_auto_columns.len(), 2);
assert_eq!(tracks.grid_auto_rows.len(), 2);
assert!(supports_declaration("grid-auto-columns", "50px minmax(20px,1fr)"));
assert!(supports_declaration("grid-auto-rows", "min-content 25%"));
assert!(!supports_declaration("grid-auto-columns", "repeat(2,50px)"));
for keyword in ["initial", "unset", "revert", "revert-layer"] {
let reset = compute_style(
"div",
Some(&format!(
"grid-auto-columns:50px;grid-auto-columns:{keyword};\
grid-auto-rows:60px;grid-auto-rows:{keyword}"
)),
);
assert!(
reset.grid_auto_columns.is_empty() && reset.grid_auto_rows.is_empty(),
"{keyword} must restore the initial automatic implicit track"
);
assert!(!reset.grid_auto_columns_inherit);
assert!(!reset.grid_auto_rows_inherit);
}
let inherited = compute_style(
"div",
Some("grid-auto-columns:inherit;grid-auto-rows:inherit"),
);
assert!(inherited.grid_auto_columns_inherit);
assert!(inherited.grid_auto_rows_inherit);
}
#[test] #[test]
fn grid_placement_longhands_preserve_the_opposite_side() { fn grid_placement_longhands_preserve_the_opposite_side() {
let style = compute_style( let style = compute_style(