Support implicit grid track sizing
This commit is contained in:
1 parent
65ed38a71d
commit
7a1dc43d3c
3 files changed
+306
No files matched your search
@@ -2233,7 +2233,36 @@ fn layout_dom_once(
|
||||
// (updated to its content width inside the block below).
|
||||
let mut child_cb_width = inh.cb_width;
|
||||
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 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 {
|
||||
style.display = inh.display;
|
||||
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_flow_root = style.flow_root;
|
||||
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 {
|
||||
2
|
||||
} else if style.overflow_clip_x {
|
||||
@@ -2705,6 +2736,14 @@ fn layout_dom_once(
|
||||
0
|
||||
};
|
||||
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 {
|
||||
pseudo.display = host_display;
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -733,6 +733,16 @@ pub struct LayoutStyle {
|
||||
// resolved to line placements on children in a later pass.
|
||||
pub grid_template_columns: 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
|
||||
/// component, so `dom` resolves the safe full-span column subset after
|
||||
/// 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() {
|
||||
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 {
|
||||
s.grid_auto_flow = flow;
|
||||
}
|
||||
|
||||
@@ -1552,6 +1552,8 @@ fn apply_value(style: &mut LayoutStyle, name: &str, value: &str) {
|
||||
style.grid_template_rows = tracks;
|
||||
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" => parse_grid_template(style, value),
|
||||
"grid" => parse_grid_shorthand(style, value),
|
||||
@@ -1786,6 +1788,8 @@ pub(crate) fn supports_declaration(name: &str, value: &str) -> bool {
|
||||
| "border-collapse"
|
||||
| "grid-template-columns"
|
||||
| "grid-template-rows"
|
||||
| "grid-auto-columns"
|
||||
| "grid-auto-rows"
|
||||
| "grid-template-areas"
|
||||
| "grid-template"
|
||||
| "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()
|
||||
})
|
||||
}
|
||||
"grid-auto-columns" | "grid-auto-rows" => {
|
||||
parse_grid_auto_track_list(value).is_some()
|
||||
}
|
||||
"color" | "-webkit-text-fill-color" | "background-color" | "border-color" => {
|
||||
parse_color(value).is_some()
|
||||
}
|
||||
@@ -2764,6 +2771,105 @@ fn track(tok: &str) -> taffy::TrackSizingFunction {
|
||||
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 {
|
||||
use taffy::MinTrackSizingFunction as M;
|
||||
let lower = tok.to_ascii_lowercase();
|
||||
@@ -6604,6 +6710,45 @@ mod tests {
|
||||
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]
|
||||
fn grid_placement_longhands_preserve_the_opposite_side() {
|
||||
let style = compute_style(
|
||||
|
||||
Reference in new issue
Block a user