Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 33 additions & 24 deletions packages/blitz-dom/src/layout/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,24 +259,42 @@ pub(crate) fn collect_table_cells(
}
doc.nodes[node_id].children = children;
}
DisplayInside::TableCell => {
// Table-cell children, plus non-table-internal children which, per the
// CSS tables spec, generate an anonymous table cell around them.
DisplayInside::TableCell
| DisplayInside::Flow
| DisplayInside::FlowRoot
| DisplayInside::Flex
| DisplayInside::Grid => {
// node.remove_damage(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
let is_cell = display.inside() == DisplayInside::TableCell;
let stylo_style = &node.primary_styles().unwrap();
let colspan: u16 = node
.attr(local_name!("colspan"))
.and_then(|val| val.parse().ok())
.unwrap_or(1);
let rowspan: u16 = node
.attr(local_name!("rowspan"))
.and_then(|val| val.parse::<u16>().ok())
.map(|v| v.clamp(1, 65534))
.unwrap_or(1);
let colspan: u16 = if is_cell {
node.attr(local_name!("colspan"))
.and_then(|val| val.parse().ok())
.unwrap_or(1)
} else {
1
};
let rowspan: u16 = if is_cell {
node.attr(local_name!("rowspan"))
.and_then(|val| val.parse::<u16>().ok())
.map(|v| v.clamp(1, 65534))
.unwrap_or(1)
} else {
1
};
let mut style = stylo_taffy::to_taffy_style(stylo_style);

if first_cell_border.is_none() {
if is_cell && first_cell_border.is_none() {
*first_cell_border = Some(stylo_style.clone_border());
}

// Cells occurring before any row are placed in an anonymous row
if *row == 0 {
*row = 1;
}

if *row == 1 {
let column = match style.size.width.tag() {
taffy::CompactLength::LENGTH_TAG => {
Expand Down Expand Up @@ -308,12 +326,14 @@ pub(crate) fn collect_table_cells(

// Zero-out cell borders is BorderCollapse is Collapse
// Borders are handled at the table level in this mode
if border_collapse == BorderCollapse::Collapse {
if is_cell && border_collapse == BorderCollapse::Collapse {
style.border = taffy::Rect::ZERO.map(style_helpers::length);
}

// The margin properties do not apply to table-internal elements
style.margin = taffy::Rect::ZERO.map(style_helpers::length);
if is_cell {
style.margin = taffy::Rect::ZERO.map(style_helpers::length);
}

// Let Taffy auto-place the column. Combined with
// `grid_auto_flow: RowDense` set on the table root, each cell
Expand All @@ -333,17 +353,6 @@ pub(crate) fn collect_table_cells(

*col += colspan;
}
DisplayInside::Flow
| DisplayInside::FlowRoot
| DisplayInside::Flex
| DisplayInside::Grid => {
node.remove_damage(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
// Probably a table caption: ignore
// println!(
// "Warning: ignoring non-table typed descendent of table ({:?})",
// display.inside()
// );
}
DisplayInside::TableColumnGroup | DisplayInside::TableColumn | DisplayInside::Table => {
node.remove_damage(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
//Ignore
Expand Down
72 changes: 72 additions & 0 deletions tests/blitz-tests/tests/table_anonymous_cell.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//! Non-table-internal children of a `display: table` element generate an
//! anonymous table cell (CSS 2.2 §17.2.1), so they must be laid out and
//! hit-testable. Regression test for the gov.uk search box, whose input
//! sits inside plain block `<div>`s that are direct children of a
//! `display: table` wrapper.

use blitz_test_harness::{Harness, HarnessOptions};

fn harness(html: &str) -> Harness {
Harness::from_html_with(
html,
HarnessOptions {
width: 400,
height: 200,
..Default::default()
},
)
}

#[test]
fn block_children_of_table_are_laid_out_as_anonymous_cells() {
let harness = harness(
r#"<html><body style="margin:0">
<div style="display:table; width:400px;">
<div id="input-wrapper" style="width:100%;">
<input id="search" type="search" style="width:100%; height:40px; margin:0; box-sizing:border-box;">
</div>
<div id="button-wrapper" style="width:40px;">
<button style="width:40px; height:40px;">Go</button>
</div>
</div>
</body></html>"#,
);

let wrapper_rect = harness.layout_rect("#input-wrapper");
assert!(
wrapper_rect.width > 0.0 && wrapper_rect.height > 0.0,
"block child of table should be laid out, got {wrapper_rect:?}"
);

let input_rect = harness.layout_rect("#search");
assert!(
input_rect.width > 0.0 && input_rect.height > 0.0,
"input inside table's block child should be laid out, got {input_rect:?}"
);

let input = harness.node("#search");
let (cx, cy) = harness.center_of("#search");
assert_eq!(
harness.hit_node(cx, cy),
input,
"input inside table's block child should be hit-testable"
);
}

#[test]
fn clicking_input_inside_table_block_child_focuses_it() {
let mut harness = harness(
r#"<html><body style="margin:0">
<div style="display:table; width:400px;">
<div style="width:100%;">
<input id="search" type="search" style="width:100%; height:40px; margin:0; box-sizing:border-box;">
</div>
</div>
</body></html>"#,
);

let input = harness.node("#search");
let (cx, cy) = harness.center_of("#search");
harness.click_at(cx, cy);
assert_eq!(harness.focused(), Some(input));
}
Loading