Skip to content

Keep cross axis margins and relative insets of items in wrapping containers (#59532) - #59532

Open
cipolleschi wants to merge 1 commit into
react:mainfrom
cipolleschi:export-D127132637
Open

cipolleschi wants to merge 1 commit into
react:mainfrom
cipolleschi:export-D127132637

Conversation

@cipolleschi

@cipolleschi cipolleschi commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Summary:

Fixes react/yoga#1856
Fixes react/yoga#1067

In wrapping containers, STEP 8 (multi line alignment) of CalculateLayout.cpp repositions items on the cross axis and drops part of each box. In react/yoga#1856 the container is a column (Yoga's default), so left is on the cross axis.

Every case now uses the leading margin, trailing margin and relativePosition. wrap-reverse mirrors positions afterwards, so it leads with the flex end margin and applies the inset after mirroring, keeping each margin on its edge and top: 10 moving the item down.

Also, a percentage inset on a flex-start item now resolves against the container's cross size, as without wrap (top: 10% in a 100x200 row: 10 to 20), and baseline rows, which run this pass even without wrap, honor bottom and ignore insets on static items.

Column container, padding 10, flex-wrap: wrap, default align-content: flex-start, item with margin 5 and left: 10 (item left; expected worked out from the spec):

align-items main this PR expected
flex-start 20 25 25
stretch 15 25 25
center 15 25 25
flex-end 15 25 25
flex-start, no inset 10 15 15

The react/yoga#1067 repro goes from top 50 to 100.

Errata

These positions have been in place for a long time, and React Native runs Yoga with YGErrataAll. So the previous behavior is kept behind a new erratum, YGErrataWrapCrossAlignmentIgnoresMarginAndInset (1 << 5). When it is set, STEP 8 and the wrap-reverse mirroring use their previous offsets. New configs leave it unset and get the fix; YGErrataAll and YGErrataClassic include it, so React Native keeps its current layout under the classic conformance, and strict gets the spec behavior. With the erratum, the main column of the table above is what you get.

The enum bindings (C++, Kotlin and TypeScript) are regenerated with enums.py.

Tests

The spec cases are gentest fixtures, eleven in YGFlexWrapTest.html and one in YGAlignItemsTest.html, with expected values from Chrome and generated C++, Java and JS tests. They cover row and column, LTR and RTL, wrap and wrap-reverse (including one sided margins), baseline, static items, a percentage inset and both issue repros. All 12 generated C++ tests fail without the fix.

tests/YGFlexWrapCrossAlignmentTest.cpp keeps 4 hand written errata cases that pin the previous positions under the erratum, including a wrapping row with align-items: flex-start and items with margin: 4, whose lines stay at top 0 and 38 under YGErrataAll and move to 4 and 42 under YGErrataNone. The positions they expect under the erratum are the ones main gives, and all 4 fail without the erratum checks in CalculateLayout.cpp.

With the change, all 884 C++ tests pass (868 plus 12 generated plus 4). Laying out 20000 random trees with YGErrataAll, every position on this branch matches the base commit exactly, and without the erratum every position matches the first commit of this PR.

Not covered here: auto cross margins in wrapping containers and baseline rows, which STEP 8 also ignores on main (react/yoga#2048).

Changelog:
[General][Fixed] - Keep cross axis margins and relative insets of items in wrapping containers, with a new erratum that keeps the previous positions under YGErrataAll and YGErrataClassic

X-link: react/yoga#2045

Test Plan:
Universal macOS Cosmo Studio app and dSYM build passed with the configuration from the failing CI job:

buck2 build fbsource//xplat/mode/cosmo/auto.py#production,macosx-universal,e2e,bundle-rn --show-json-output --config apple.fb_bundle_version=1101344647 //xplat/arfx/cosmo/studio/app/mac:Cosmo "//xplat/arfx/cosmo/studio/app/mac:Cosmo[dsym]"

BUILD SUCCEEDED, exit code 0.
https://www.internalfb.com/buck2/fbae0652-2658-4575-a895-b8a3d54d386f

Yoga regression tests:

buck2 test //xplat/yoga:tests

Pass 893. Fail 0. Skip 0. Infra Failure 0. Build failure 0.
https://www.internalfb.com/intern/testinfra/testrun/12666374165098828

C++ stable API snapshot validation:

buck2 run //xplat/js/react-native-github/scripts/cxx-api:api-snapshot -- --validate

Passed, exit code 0.

The base includes the landed Boost Filesystem fix in D127790095. The Yoga changes are unchanged apart from merging onto the current base. App launch and Cosmo E2E execution were not tested by the local build.

Reviewed By: javache

Differential Revision: D127132637

Pulled By: cipolleschi

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Oct 27, 2026
@meta-codesync

meta-codesync Bot commented Oct 27, 2026

Copy link
Copy Markdown

@cipolleschi has exported this pull request. If you are a Meta employee, you can view the originating Diff in D127132637.

…ainers (react#59532)

Summary:

Fixes react/yoga#1856
Fixes react/yoga#1067

In wrapping containers, STEP 8 (multi line alignment) of `CalculateLayout.cpp` repositions items on the cross axis and drops part of each box. In react/yoga#1856 the container is a column (Yoga's default), so `left` is on the cross axis.

- `flex-start` uses `computeFlexStartPosition` (the inset) where `computeFlexStartMargin` belongs. v2.0.1 used the margin, so this is a 3.0 regression from react/yoga#1561.
- `stretch`, `center` and `flex-end` never add the relative inset.
- `center` centers the border box instead of the margin box, which is react/yoga#1067.

Every case now uses the leading margin, trailing margin and `relativePosition`. `wrap-reverse` mirrors positions afterwards, so it leads with the flex end margin and applies the inset after mirroring, keeping each margin on its edge and `top: 10` moving the item down.

Also, a percentage inset on a `flex-start` item now resolves against the container's cross size, as without wrap (`top: 10%` in a 100x200 row: 10 to 20), and baseline rows, which run this pass even without wrap, honor `bottom` and ignore insets on static items.

Column container, padding 10, `flex-wrap: wrap`, default `align-content: flex-start`, item with margin 5 and `left: 10` (item left; expected worked out from the spec):

| `align-items` | main | this PR | expected |
|---|---|---|---|
| `flex-start` | 20 | 25 | 25 |
| `stretch` | 15 | 25 | 25 |
| `center` | 15 | 25 | 25 |
| `flex-end` | 15 | 25 | 25 |
| `flex-start`, no inset | 10 | 15 | 15 |

The react/yoga#1067 repro goes from top 50 to 100.

### Errata

These positions have been in place for a long time, and React Native runs Yoga with `YGErrataAll`. So the previous behavior is kept behind a new erratum, `YGErrataWrapCrossAlignmentIgnoresMarginAndInset` (`1 << 5`). When it is set, STEP 8 and the `wrap-reverse` mirroring use their previous offsets. New configs leave it unset and get the fix; `YGErrataAll` and `YGErrataClassic` include it, so React Native keeps its current layout under the classic conformance, and strict gets the spec behavior. With the erratum, the `main` column of the table above is what you get.

The enum bindings (C++, Kotlin and TypeScript) are regenerated with `enums.py`.

### Tests

The spec cases are gentest fixtures, eleven in `YGFlexWrapTest.html` and one in `YGAlignItemsTest.html`, with expected values from Chrome and generated C++, Java and JS tests. They cover row and column, LTR and RTL, `wrap` and `wrap-reverse` (including one sided margins), baseline, static items, a percentage inset and both issue repros. All 12 generated C++ tests fail without the fix.

`tests/YGFlexWrapCrossAlignmentTest.cpp` keeps 4 hand written errata cases that pin the previous positions under the erratum, including a wrapping row with `align-items: flex-start` and items with `margin: 4`, whose lines stay at top 0 and 38 under `YGErrataAll` and move to 4 and 42 under `YGErrataNone`. The positions they expect under the erratum are the ones `main` gives, and all 4 fail without the erratum checks in `CalculateLayout.cpp`.

With the change, all 884 C++ tests pass (868 plus 12 generated plus 4). Laying out 20000 random trees with `YGErrataAll`, every position on this branch matches the base commit exactly, and without the erratum every position matches the first commit of this PR.

Not covered here: `auto` cross margins in wrapping containers and baseline rows, which STEP 8 also ignores on `main` (react/yoga#2048).

Changelog:
[General][Fixed] - Keep cross axis margins and relative insets of items in wrapping containers, with a new erratum that keeps the previous positions under YGErrataAll and YGErrataClassic

X-link: react/yoga#2045

Test Plan:
Universal macOS Cosmo Studio app and dSYM build passed with the configuration from the failing CI job:
```bash
buck2 build fbsource//xplat/mode/cosmo/auto.py#production,macosx-universal,e2e,bundle-rn --show-json-output --config apple.fb_bundle_version=1101344647 //xplat/arfx/cosmo/studio/app/mac:Cosmo "//xplat/arfx/cosmo/studio/app/mac:Cosmo[dsym]"
```
`BUILD SUCCEEDED`, exit code 0.
https://www.internalfb.com/buck2/fbae0652-2658-4575-a895-b8a3d54d386f

Yoga regression tests:
```bash
buck2 test //xplat/yoga:tests
```
Pass 893. Fail 0. Skip 0. Infra Failure 0. Build failure 0.
https://www.internalfb.com/intern/testinfra/testrun/12666374165098828

C++ stable API snapshot validation:
```bash
buck2 run //xplat/js/react-native-github/scripts/cxx-api:api-snapshot -- --validate
```
Passed, exit code 0.

The base includes the landed Boost Filesystem fix in D127790095. The Yoga changes are unchanged apart from merging onto the current base. App launch and Cosmo E2E execution were not tested by the local build.

Reviewed By: javache

Differential Revision: D127132637

Pulled By: cipolleschi
@meta-codesync meta-codesync Bot changed the title Keep cross axis margins and relative insets of items in wrapping containers Keep cross axis margins and relative insets of items in wrapping containers (#59532) Oct 27, 2026
@github-actions

Copy link
Copy Markdown

Visual regression

1 visual difference(s) at 7ae2cf2b4937. Visual differences need maintainer approval. Review the report, then apply visual-change-approved.

android: no capture

ios touchable/highlight/default: Changed, 42 px (0.039%)

diff

Expected and Captured
Expected Captured
expected captured

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. meta-exported p: Facebook Partner: Facebook Partner

Projects

None yet

2 participants