diff --git a/CHANGELOG.md b/CHANGELOG.md index c8861ed23..18fc7f5a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,8 @@ - Block/Flexbox/Grid: a container's own end-side padding (right padding for LTR, left padding for RTL, and bottom padding) is now only included in its `content_size` when the container is a scroll container (i.e. has `overflow` other than `visible`/`clip` in either axis). Per the [CSS Overflow spec](https://www.w3.org/TR/css-overflow-3/#scrollable), boxes that are not scroll containers do not extend their scrollable overflow region by their own padding, so overflowing content within an ordinary padded box no longer propagates spuriously enlarged content sizes to ancestor scroll containers +- Leaf: `compute_leaf_layout` now follows the same convention as block/flexbox/grid for its `content_size`: the node's own end-side padding (right padding for LTR, left padding for RTL, and bottom padding) is only included when the node is a scroll container. Previously both the start and end padding were included unconditionally, over-reporting the content size of ordinary padded leaves whose measured content overflows + - Grid: the first baselines of grid items which are scroll containers are now clamped to the item's border box (matching the existing flexbox behaviour, per the [CSSWG resolution](https://github.com/w3c/csswg-drafts/issues/7660)) - Flexbox: items with `align-self: baseline` and an `auto` cross-axis margin no longer participate in baseline alignment, per [CSS Flexbox ยง8.3](https://www.w3.org/TR/css-flexbox-1/#baseline-participation). Previously such items were still counted when deciding whether a line performs baseline alignment, had their baselines measured, and could affect the container's own first baseline diff --git a/src/compute/leaf.rs b/src/compute/leaf.rs index a900f8d4c..9ddac7f5d 100644 --- a/src/compute/leaf.rs +++ b/src/compute/leaf.rs @@ -150,10 +150,25 @@ where }; let size = size.maybe_max(padding_border.sum_axes().map(Some)); + // A scroll container's own padding at the end of the content is part of its scrollable + // overflow region, so it is included in the content size. Boxes that are not scroll + // containers do not extend their overflow region by their own padding. + #[cfg(feature = "content_size")] + let content_size = { + let is_scroll_container = style.overflow().x.is_scroll_container() || style.overflow().y.is_scroll_container(); + let is_rtl = style.direction().is_rtl(); + let start_padding = if is_rtl { padding.right } else { padding.left }; + let end_padding = if is_rtl { padding.left } else { padding.right }; + Size { + width: start_padding + measured_size.width + if is_scroll_container { end_padding } else { 0.0 }, + height: padding.top + measured_size.height + if is_scroll_container { padding.bottom } else { 0.0 }, + } + }; + LayoutOutput { size, #[cfg(feature = "content_size")] - content_size: measured_size + padding.sum_axes(), + content_size, baselines: Baselines::NONE, top_margin: CollapsibleMarginSet::ZERO, bottom_margin: CollapsibleMarginSet::ZERO, diff --git a/tests/hand_written/scroll_size.rs b/tests/hand_written/scroll_size.rs index c1aa361cd..fadf72f4f 100644 --- a/tests/hand_written/scroll_size.rs +++ b/tests/hand_written/scroll_size.rs @@ -5,7 +5,7 @@ use taffy::geometry::Point; use taffy::prelude::*; use taffy::style::{BoxSizing, Display, Overflow}; -use taffy_test_helpers::new_test_tree; +use taffy_test_helpers::{new_test_tree, test_measure_function, TestNodeContext}; const CONTENT: f32 = 1000.0; const CONTAINER: f32 = 200.0; @@ -130,6 +130,45 @@ fn content_size_excludes_the_own_padding_of_a_non_scroll_container() { } } +fn measured_leaf(overflow: Overflow, padding: Rect) -> Layout { + let mut tree = new_test_tree(); + let node = tree + .new_leaf_with_context( + Style { + box_sizing: BoxSizing::BorderBox, + size: Size { width: length(300.0), height: length(CONTAINER) }, + padding, + overflow: Point { x: overflow, y: overflow }, + ..Default::default() + }, + TestNodeContext::fixed(100.0, CONTENT), + ) + .unwrap(); + + tree.compute_layout_with_measure(node, Size::MAX_CONTENT, test_measure_function).unwrap(); + *tree.layout(node).unwrap() +} + +#[test] +fn leaf_content_size_includes_the_containers_own_padding() { + for (top, bottom) in [(PADDING, 0.0), (0.0, PADDING), (PADDING, PADDING), (0.0, 0.0)] { + let layout = measured_leaf(Overflow::Scroll, edge(top, bottom)); + + assert_eq!(layout.content_size.height, top + CONTENT + bottom, "Leaf with padding {top}/{bottom}"); + } +} + +#[test] +fn leaf_content_size_excludes_the_own_padding_of_a_non_scroll_container() { + for (top, bottom) in [(PADDING, 0.0), (0.0, PADDING), (PADDING, PADDING), (0.0, 0.0)] { + let layout = measured_leaf(Overflow::Visible, edge(top, bottom)); + + // A box that is not a scroll container does not extend its scrollable overflow + // region by its own padding: only the content contributes. + assert_eq!(layout.content_size.height, top + CONTENT, "Leaf with padding {top}/{bottom}"); + } +} + #[test] fn scroll_height_accounts_for_the_containers_own_padding() { for display in [Display::Block, Display::Flex, Display::Grid] {