From 6673edb52cb9fe43f92d091f94fe50503157b64d Mon Sep 17 00:00:00 2001 From: dagur Date: Thu, 3 Sep 2026 10:48:28 +0000 Subject: [PATCH] Layout breaks when the first element has a different minWidth than the rest --- tests/YGFirstChildMinWidthTest.cpp | 72 ++++++++++++++++++++++++++++++ yoga/algorithm/CalculateLayout.cpp | 17 ++++++- 2 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 tests/YGFirstChildMinWidthTest.cpp diff --git a/tests/YGFirstChildMinWidthTest.cpp b/tests/YGFirstChildMinWidthTest.cpp new file mode 100644 index 0000000000..7a4c85184c --- /dev/null +++ b/tests/YGFirstChildMinWidthTest.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +// Regression test for https://github.com/react/yoga/issues/2006 +// A row of three growable children (flexGrow/flexShrink 1, maxWidth 180) inside +// a 540px container. When the *first* child has a larger minWidth than the +// rest, the first free-space pass used to freeze every item at its max while +// also draining the remaining free space to exactly zero, leaving the second +// pass with nothing to distribute. The children then collapsed to their +// minWidths (60/30/30) instead of growing to fill the row (180/180/180). + +#include +#include + +namespace { + +// Lay out a 540px row containing three children, each with flexGrow/ +// flexShrink of 1, maxWidth of `maxW` and the given minWidths, and assert +// every child grows to maxWidth (the row exactly fills the container). +void expectAllGrowToMax(float minWidth0, float minWidth1, float minWidth2) { + YGConfigRef config = YGConfigNew(); + YGNodeRef root = YGNodeNewWithConfig(config); + YGNodeStyleSetWidth(root, 540.0f); + YGNodeStyleSetFlexDirection(root, YGFlexDirectionRow); + + float minWidths[] = {minWidth0, minWidth1, minWidth2}; + for (size_t i = 0; i < 3; i++) { + YGNodeRef child = YGNodeNewWithConfig(config); + YGNodeStyleSetFlexGrow(child, 1.0f); + YGNodeStyleSetFlexShrink(child, 1.0f); + YGNodeStyleSetMaxWidth(child, 180.0f); + YGNodeStyleSetHeight(child, 30.0f); + YGNodeStyleSetMinWidth(child, minWidths[i]); + YGNodeInsertChild(root, child, i); + } + + YGNodeCalculateLayout(root, 540.0f, 30.0f, YGDirectionLTR); + + for (size_t i = 0; i < 3; i++) { + YGNodeRef child = YGNodeGetChild(root, i); + EXPECT_NEAR(180.0f, YGNodeLayoutGetWidth(child), 1e-3f) + << "child[" << i + << "] should grow to maxWidth, not collapse to its mins flexible items, so every item's tentative size must be + // computed against the *original* totals. The totals are still reduced as + // items get frozen below (so the second pass can redistribute), but those + // reduced values must not feed back into the fair-share calculation for the + // remaining items: doing so inflates their tentative size and can freeze + // items that should still be able to grow/shrink (see + // https://github.com/react/yoga/issues/2006). + const float originalTotalFlexGrowFactors = + flexLine.layout.totalFlexGrowFactors; + const float originalTotalFlexShrinkScaledFactors = + flexLine.layout.totalFlexShrinkScaledFactors; + for (auto currentLineChild : flexLine.itemsInFlow) { float childFlexBasis = boundAxisWithinMinAndMax( currentLineChild, @@ -1299,7 +1312,7 @@ static void distributeFreeSpaceFirstPass( flexShrinkScaledFactor != 0) { baseMainSize = childFlexBasis + flexLine.layout.remainingFreeSpace / - flexLine.layout.totalFlexShrinkScaledFactors * + originalTotalFlexShrinkScaledFactors * flexShrinkScaledFactor; boundMainSize = boundAxisWithAutoMin( currentLineChild, @@ -1329,7 +1342,7 @@ static void distributeFreeSpaceFirstPass( if (yoga::isDefined(flexGrowFactor) && flexGrowFactor != 0) { baseMainSize = childFlexBasis + flexLine.layout.remainingFreeSpace / - flexLine.layout.totalFlexGrowFactors * flexGrowFactor; + originalTotalFlexGrowFactors * flexGrowFactor; boundMainSize = boundAxis( currentLineChild, mainAxis,

AltStyle によって変換されたページ (->オリジナル) /