Inconsistent text wrap behavior with flexbox #1730

Closed
opened 2024-10-24 21:23:27 -07:00 by kzlar · 12 comments
kzlar commented 2024-10-24 21:23:27 -07:00 (Migrated from github.com)

Report

Issues and Steps to Reproduce

Snack Repro: https://snack.expo.dev/@meata/text-bug?platform=ios
Select the iOS preview.

The gist is this: I have a horizontal stack with two text fields, when the first field gets too long I expect it to wrap and take the minimum width it can. This doesn't seem to be the case, worse, it seems to behave differently depending on the number of letters in the first field. The first section shows the behavior I expect, the second is the weird behavior. With both the total width is the same, only difference is the additional letter.

image

Same issue can be observed on Android but the text needs to be changed a bit to make it work.
On web it at least seems consistent, even though I'm not sure why it does not shrink.

Expected Behavior

Explained above, but generally I expect it to at least behave consistently (either shrink or don't)

Actual Behavior

Explained above, adding a single character changes the layout from shrinking to not shrinking.

Link to Code

https://snack.expo.dev/@meata/text-bug?platform=ios

# Report - [X] I have searched [existing issues](https://github.com/facebook/yoga/issues) and this is not a duplicate # Issues and Steps to Reproduce Snack Repro: https://snack.expo.dev/@meata/text-bug?platform=ios Select the iOS preview. The gist is this: I have a horizontal stack with two text fields, when the first field gets too long I expect it to wrap and take the minimum width it can. This doesn't seem to be the case, worse, it seems to behave differently depending on the number of letters in the first field. The first section shows the behavior I expect, the second is the weird behavior. With both the total width is the same, only difference is the additional letter. <img width="249" alt="image" src="https://github.com/user-attachments/assets/a1534682-5b6a-4ece-94af-03f4c541dcaa"> Same issue can be observed on Android but the text needs to be changed a bit to make it work. On web it at least seems consistent, even though I'm not sure why it does not shrink. # Expected Behavior Explained above, but generally I expect it to at least behave consistently (either shrink or don't) # Actual Behavior Explained above, adding a single character changes the layout from shrinking to not shrinking. # Link to Code https://snack.expo.dev/@meata/text-bug?platform=ios
NickGerleman commented 2024-10-24 21:52:32 -07:00 (Migrated from github.com)

There are a couple of related things here:

  1. Yoga currently incorrectly measures initial flex-basis using fit-content instead of max-content (apparently very old versions of Safari actually also did this?). So the initial contributions are the size fitting the container, instead of the max size given infinite width.
  2. Yoga asks underlying platform (React Native) the size of text under this constraint (fit-content = YGMeasureModeAtMost). In this case, the answer given by legacy renderer seems potentially wrong (only accounts for max line width).

@kzlar is there any way you could repro this against RN new arch (default in RN 0.76/Expo 52)? It goes through different measurement path.

There are a couple of related things here: 1. Yoga currently incorrectly measures initial flex-basis using fit-content instead of max-content (apparently very old versions of Safari actually also did this?). So the initial contributions are the size fitting the container, instead of the max size given infinite width. 2. Yoga asks underlying platform (React Native) the size of text under this constraint (fit-content = YGMeasureModeAtMost). In this case, the answer given by legacy renderer seems potentially wrong (only accounts for max line width). @kzlar is there any way you could repro this against RN new arch (default in RN 0.76/Expo 52)? It goes through different measurement path.
NickGerleman commented 2024-10-24 22:12:55 -07:00 (Migrated from github.com)

For #2, there's a good chance on Android that we aren't filling bounds of multiline text ala web in this logic 94fdc38822/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java (L661)

For #2, there's a good chance on Android that we aren't filling bounds of multiline text ala web in this logic https://github.com/facebook/react-native/blob/94fdc388220399d22ee2ada76b7aed847d8c0eea/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java#L661
s77rt commented 2024-11-07 06:40:06 -08:00 (Migrated from github.com)

Problem:

The first text is already too long that it wraps in 2 lines, making the width enough => It won't shrink
The second text will initially fit in one line but since the items exceed the parent width => it will shrink. The calculated width after shrinking is correct but that new width can no longer hold that text in one line, thus it wraps.

In the second text, after the text shrinks the new layout width is calculated with mode Exactly (SizingMode::StretchFit)

Before shrinking After shrinking
Screenshot 2024-11-07 at 3 16 51 PM Screenshot 2024-11-07 at 3 21 39 PM

Solution

If the child shrank, use mode AtMost (SizingMode::FitContent)

iOS Android
Screenshot 2024-11-07 at 3 28 37 PM Screenshot 2024-11-07 at 3 28 36 PM
## Problem: The first text is already too long that it wraps in 2 lines, making the width enough => It won't shrink The second text will initially fit in one line but since the items exceed the parent width => it will shrink. The calculated width after shrinking is correct but that new width can no longer hold that text in one line, thus it wraps. In the second text, after the text shrinks the new layout width is calculated with mode `Exactly` (`SizingMode::StretchFit`) | Before shrinking | After shrinking | |:----------------:|:---------------:| | <img width="480" alt="Screenshot 2024-11-07 at 3 16 51 PM" src="https://github.com/user-attachments/assets/86403cdd-cfbb-4b9e-a78a-47f567b9b88d"> | <img width="480" alt="Screenshot 2024-11-07 at 3 21 39 PM" src="https://github.com/user-attachments/assets/1b0a9ce9-ab8f-4dc1-9c68-b77c8bd198e6"> | ## Solution If the child shrank, use mode `AtMost` (`SizingMode::FitContent`) | iOS | Android | |:---:|:-------:| | <img width="515" alt="Screenshot 2024-11-07 at 3 28 37 PM" src="https://github.com/user-attachments/assets/7b3ffb69-80dd-4548-a856-920165161c03"> | <img width="480" alt="Screenshot 2024-11-07 at 3 28 36 PM" src="https://github.com/user-attachments/assets/7f5e6bcd-1b9c-4bff-affe-0482a720e826"> |
delphinebugner commented 2024-11-07 06:56:24 -08:00 (Migrated from github.com)

Oh wah, thanks @s77rt! I had also looked into @kzlar issue and did not understand why the first text was fitting it's container; now it's clear! It's actually not affected by flexShrink=1 as it's already wrapping in regard to its full parent's width.

Second text is actually the standard, using the Exactly mode as you said.
Regarding your solution, would'nt it be a big breaking change if we start using AtMost for shrinking children?

Oh wah, thanks @s77rt! I had also looked into @kzlar issue and did not understand why the _first_ text was fitting it's container; now it's clear! It's actually not affected by `flexShrink=1` as it's _already_ wrapping in regard to its **full** parent's width. Second text is actually the standard, using the `Exactly` mode as you said. Regarding your solution, would'nt it be a big breaking change if we start using `AtMost` for shrinking children?
s77rt commented 2024-11-07 07:42:29 -08:00 (Migrated from github.com)

@delphinebugner Since we are using less width than what we have allocated we may have unfilled space (even with flexGrow). Now I can't get the yellow rectangle to take the remaining space.

@delphinebugner Since we are using less width than what we have allocated we may have unfilled space (even with `flexGrow`). Now I can't get the yellow rectangle to take the remaining space.
NickGerleman commented 2024-11-07 16:30:06 -08:00 (Migrated from github.com)

@s77rt when I looked at behavior on web, sizing explicitly to fit-content, wrapped paragraph took full available width instead of width of max line. So, when we shrink, we only shrink to amount of space needed to also add inflexible child.

Ie in top example, yellow inflexible child should hug the right.

So I think measure function logic in RN needs to be changed to return available width when text is wrapped, instead of longest line width.

Separately, Yoga should be calculating initial flex basis as max content instead of fit-content, but that’s a gnarlier bug.

@s77rt when I looked at behavior on web, sizing explicitly to fit-content, wrapped paragraph took full available width instead of width of max line. So, when we shrink, we only shrink to amount of space needed to also add inflexible child. Ie in top example, yellow inflexible child should hug the right. So I think measure function logic in RN needs to be changed to return available width when text is wrapped, instead of longest line width. Separately, Yoga should be calculating initial flex basis as max content instead of fit-content, but that’s a gnarlier bug.
s77rt commented 2024-11-08 00:52:51 -08:00 (Migrated from github.com)

Ah I got it. It does make sense in a way and it's consistent with web. Will look into this one then

Ah I got it. It does make sense in a way and it's consistent with web. Will look into this one then
kzlar commented 2024-11-22 19:10:16 -08:00 (Migrated from github.com)

Appreciate the prompt help on this folks :)

Appreciate the prompt help on this folks :)
weihanyau commented 2025-03-24 01:35:31 -07:00 (Migrated from github.com)

@s77rt Hi appreciate the effort on making the behaviour more consistent, but is it possible to make it an option for the other way around where the wrapped paragraph took the width of max line instead of full available width as we have a use case in our application for this behaviour? Thanks

@s77rt Hi appreciate the effort on making the behaviour more consistent, but is it possible to make it an option for the other way around where the wrapped paragraph took the width of max line instead of full available width as we have a use case in our application for this behaviour? Thanks
s77rt commented 2025-03-25 12:52:52 -07:00 (Migrated from github.com)

@weihanyau I don't think that something that may land upstream. You can try either path RN or make use of onTextLayout

@weihanyau I don't think that something that may land upstream. You can try either path RN or make use of `onTextLayout`
weihanyau commented 2025-03-26 01:05:35 -07:00 (Migrated from github.com)

@weihanyau I don't think that something that may land upstream. You can try either path RN or make use of onTextLayout

@s77rt Thanks for the response. May I know what is "path RN"? Can't seem to find anything related to it

> [@weihanyau](https://github.com/weihanyau) I don't think that something that may land upstream. You can try either path RN or make use of `onTextLayout` @s77rt Thanks for the response. May I know what is "path RN"? Can't seem to find anything related to it
s77rt commented 2025-03-26 01:38:33 -07:00 (Migrated from github.com)

@weihanyau I'm assuming you are facing this on react native which you can find the PR on https://github.com/facebook/react-native/pull/47435.

May I know what is "path RN"?

Sorry a typo, I meant to patch reactnative, you can use patch-package to revert the above PR changes

@weihanyau I'm assuming you are facing this on react native which you can find the PR on https://github.com/facebook/react-native/pull/47435. > May I know what is "path RN"? Sorry a typo, I meant to patch reactnative, you can use [patch-package](https://www.npmjs.com/package/patch-package) to revert the above PR changes
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: DaddyFrosty/yoga#1730
No description provided.