Skip to content

Commit 6c485bc

Browse files
expo-tuft[bot]ide
authored andcommitted
vendor: CSS Grid layout algorithm from Yoga PR react#1894 (unlanded upstream)
Grid lanes (css-grid-3) is defined on top of Grid's track sizing algorithm: it imports <track-size>, minmax(), fr, repeat(), auto-fill/auto-fit and line placement from css-grid-2 by name. That algorithm does not exist in our tree — PR 1/9 of the upstream series landed the style types (GridTrackSize, GridLine, gridTemplateColumns/Rows) but not the algorithm behind them. Rather than write a second one-axis track sizer, vendor the upstream implementation verbatim: react/yoga#1894 "CSS Grid 2/9: Grid layout algorithm" by Nishan Bende (intergalacticspacehighway), open since 2026-02-25 algorithm/grid/AutoPlacement.h css-grid-1 §8.5 auto-placement algorithm/grid/GridLayout.{h,cpp} entry point, implicit tracks algorithm/grid/GridTrack.h per-track layout state algorithm/grid/TrackSizing.h css-grid-2 §12 track sizing TrackSizing is parameterized by Dimension — it runs per axis — which is exactly the shape grid lanes needs, since lanes sizes tracks in the grid axis only and flows content in the other. Not our code: nothing in algorithm/grid/ is modified, so that directory can be deleted wholesale and replaced when the upstream series lands instead of being merged. Only the integration points are ours, and only because the PR could not supply them — our CalculateLayout.cpp has diverged (block formatting contexts) and our Yoga is 197 commits behind: - CalculateLayout.cpp: the Display::Grid dispatch, next to Display::Block - yoga/CMakeLists.txt: an explicit algorithm/grid/*.cpp glob, since CMake's ** matches one level; Android builds through this same file and iOS globs recursively via the podspec The PR's YGNodeStyle.cpp hunk is deliberately omitted: it adds a fallback return to silence GCC -Wreturn-type, and our tree already handles those switches better with fatalWithMessage("Unknown YGGridTrackType"). Verified, not assumed: verify-grid-vendor.sh builds yogacore with the vendored directory and lays out display: grid; width: 600px; gap: 10px; grid-template-columns: 100px 1fr 2fr; grid-template-rows: 50px auto; comparing every coordinate against the same CSS rendered by real Safari (grid-vendor-verify.html, read via safaridriver). 25/25 values match exactly, including fr distribution (160/320 of 480 remaining) and the auto row resolving to its tallest item. Upstream status: react#1894 is being re-split after review — react#1923 (data structures) and react#1994 (auto-placement) are open, but the track-sizing split has not been posted, which is why this vendors the original PR. See algorithm/grid/README-VENDORED.md. Co-authored-by: ide <379606+ide@users.noreply.github.com> (cherry picked from commit 08d6986)
1 parent 497d79b commit 6c485bc

12 files changed

Lines changed: 3499 additions & 8 deletions

File tree

‎grid-vendor-verify.cpp‎

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Smoke test for the vendored CSS Grid algorithm (Yoga PR #1894), pinned
2+
// against real Safari for the identical layout (see /tmp/gl/grid.html).
3+
//
4+
// display: grid; width: 600px; gap: 10px;
5+
// grid-template-columns: 100px 1fr 2fr;
6+
// grid-template-rows: 50px auto;
7+
// six children with fixed heights 20/30/25/40/15/35
8+
#include <yoga/Yoga.h>
9+
#include <cstdio>
10+
#include <cstdlib>
11+
12+
struct Expected {
13+
const char* name;
14+
float x, y, w, h;
15+
};
16+
17+
// Ground truth read out of Safari 26 via safaridriver.
18+
static const Expected kSafari[] = {
19+
{"a", 0.0f, 0.0f, 100.0f, 20.0f},
20+
{"b", 110.0f, 0.0f, 160.0f, 30.0f},
21+
{"c", 280.0f, 0.0f, 320.0f, 25.0f},
22+
{"d", 0.0f, 60.0f, 100.0f, 40.0f},
23+
{"e", 110.0f, 60.0f, 160.0f, 15.0f},
24+
{"f", 280.0f, 60.0f, 320.0f, 35.0f},
25+
};
26+
static const float kSafariContainerHeight = 100.0f;
27+
28+
int main() {
29+
const float heights[] = {20.0f, 30.0f, 25.0f, 40.0f, 15.0f, 35.0f};
30+
31+
YGNodeRef root = YGNodeNew();
32+
YGNodeStyleSetDisplay(root, YGDisplayGrid);
33+
YGNodeStyleSetWidth(root, 600.0f);
34+
YGNodeStyleSetGap(root, YGGutterAll, 10.0f);
35+
36+
YGNodeStyleSetGridTemplateColumnsCount(root, 3);
37+
YGNodeStyleSetGridTemplateColumn(root, 0, YGGridTrackTypePoints, 100.0f);
38+
YGNodeStyleSetGridTemplateColumn(root, 1, YGGridTrackTypeFr, 1.0f);
39+
YGNodeStyleSetGridTemplateColumn(root, 2, YGGridTrackTypeFr, 2.0f);
40+
41+
YGNodeStyleSetGridTemplateRowsCount(root, 2);
42+
YGNodeStyleSetGridTemplateRow(root, 0, YGGridTrackTypePoints, 50.0f);
43+
YGNodeStyleSetGridTemplateRow(root, 1, YGGridTrackTypeAuto, 0.0f);
44+
45+
for (size_t i = 0; i < 6; i++) {
46+
YGNodeRef child = YGNodeNew();
47+
YGNodeStyleSetHeight(child, heights[i]);
48+
YGNodeInsertChild(root, child, i);
49+
}
50+
51+
YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR);
52+
53+
int failures = 0;
54+
auto check = [&](const char* what, float actual, float expected) {
55+
const bool ok = std::abs(actual - expected) < 0.01f;
56+
if (!ok) {
57+
failures++;
58+
}
59+
std::printf(
60+
" %-4s %-2s %7.2f %s %7.2f\n",
61+
what,
62+
ok ? "ok" : "XX",
63+
actual,
64+
ok ? "==" : "!=",
65+
expected);
66+
};
67+
68+
std::printf("container height:\n");
69+
check("h", YGNodeLayoutGetHeight(root), kSafariContainerHeight);
70+
71+
for (size_t i = 0; i < 6; i++) {
72+
const YGNodeRef child = YGNodeGetChild(root, i);
73+
const Expected& e = kSafari[i];
74+
std::printf("item %s:\n", e.name);
75+
check("x", YGNodeLayoutGetLeft(child), e.x);
76+
check("y", YGNodeLayoutGetTop(child), e.y);
77+
check("w", YGNodeLayoutGetWidth(child), e.w);
78+
check("h", YGNodeLayoutGetHeight(child), e.h);
79+
}
80+
81+
YGNodeFreeRecursive(root);
82+
std::printf(
83+
"\n%s (%d mismatches vs Safari)\n",
84+
failures == 0 ? "PASS" : "FAIL",
85+
failures);
86+
return failures == 0 ? 0 : 1;
87+
}

‎grid-vendor-verify.html‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<!DOCTYPE html>
2+
<html><head><meta charset="utf-8"><style>
3+
* { margin: 0; padding: 0; box-sizing: border-box; }
4+
body { margin: 0; }
5+
#g {
6+
display: grid;
7+
width: 600px;
8+
grid-template-columns: 100px 1fr 2fr;
9+
grid-template-rows: 50px auto;
10+
gap: 10px;
11+
}
12+
#g > div { background: #cde; }
13+
</style></head>
14+
<body>
15+
<div id="g">
16+
<div id="a" style="height:20px"></div>
17+
<div id="b" style="height:30px"></div>
18+
<div id="c" style="height:25px"></div>
19+
<div id="d" style="height:40px"></div>
20+
<div id="e" style="height:15px"></div>
21+
<div id="f" style="height:35px"></div>
22+
</div>
23+
<script>
24+
window.__rects = (function () {
25+
const out = {};
26+
const g = document.getElementById('g');
27+
const gr = g.getBoundingClientRect();
28+
out.container = {w: gr.width, h: gr.height};
29+
for (const id of ['a','b','c','d','e','f']) {
30+
const r = document.getElementById(id).getBoundingClientRect();
31+
out[id] = {x: r.left - gr.left, y: r.top - gr.top, w: r.width, h: r.height};
32+
}
33+
return out;
34+
})();
35+
</script>
36+
</body></html>

‎packages/react-native/ReactCommon/yoga/yoga/CMakeLists.txt‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ include(${YOGA_ROOT}/cmake/project-defaults.cmake)
2020

2121
file(GLOB SOURCES CONFIGURE_DEPENDS
2222
${CMAKE_CURRENT_SOURCE_DIR}/*.cpp
23-
${CMAKE_CURRENT_SOURCE_DIR}/**/*.cpp)
23+
${CMAKE_CURRENT_SOURCE_DIR}/**/*.cpp
24+
${CMAKE_CURRENT_SOURCE_DIR}/algorithm/grid/*.cpp)
2425

2526
add_library(yogacore STATIC ${SOURCES})
2627

‎packages/react-native/ReactCommon/yoga/yoga/algorithm/CalculateLayout.cpp‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include <yoga/algorithm/PixelGrid.h>
3030
#include <yoga/algorithm/SizingMode.h>
3131
#include <yoga/algorithm/TrailingPosition.h>
32+
#include <yoga/algorithm/grid/GridLayout.h>
3233
#include <yoga/debug/AssertFatal.h>
3334
#include <yoga/debug/Log.h>
3435
#include <yoga/event/event.h>
@@ -2826,6 +2827,27 @@ static void calculateLayoutImpl(
28262827
// current node as they will not be traversed
28272828
cleanupContentsNodesRecursively(node, performLayout);
28282829

2830+
// Grid formatting context: `display: grid` uses the dedicated grid layout
2831+
// path rather than the flex algorithm below. VENDORED — see
2832+
// algorithm/grid/README-VENDORED.md.
2833+
if (node->style().display() == Display::Grid) {
2834+
calculateGridLayoutInternal(
2835+
node,
2836+
availableWidth,
2837+
availableHeight,
2838+
ownerDirection,
2839+
widthSizingMode,
2840+
heightSizingMode,
2841+
ownerWidth,
2842+
ownerHeight,
2843+
performLayout,
2844+
reason,
2845+
layoutMarkerData,
2846+
depth,
2847+
generationCount);
2848+
return;
2849+
}
2850+
28292851
// Block formatting context: `display: block` uses a dedicated block layout
28302852
// path rather than the flex algorithm below (text-children-plan.md §3.A/§4.5).
28312853
if (node->style().display() == Display::Block) {

0 commit comments

Comments
 (0)