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).
|
// (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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
Reference in new issue
Block a user