fix: map text-align start/end to left/right for RN compatibility - #397
fix: map text-align start/end to left/right for RN compatibility#397mubeess wants to merge 3 commits into
Conversation
There was a problem hiding this comment.
The mapping is right, but the description undersells it — and "pragmatic fallback" invites a follow-up that would break it.
RN's left/right are already logical on both native platforms; physical alignment is not expressible at all. Links pinned at v0.86.0:
Android <Text> — TextLayoutManager.getTextAlignment branches only on "center" and "right":
var alignment =
if (swapNormalAndOpposite) Layout.Alignment.ALIGN_OPPOSITE
else Layout.Alignment.ALIGN_NORMAL
if (alignmentAttr == null) return alignment
if (alignmentAttr == "center") {
alignment = Layout.Alignment.ALIGN_CENTER
} else if (alignmentAttr == "right") {
alignment =
if (swapNormalAndOpposite) Layout.Alignment.ALIGN_NORMAL
else Layout.Alignment.ALIGN_OPPOSITE
}"left" has no branch — it falls through to ALIGN_NORMAL (start), same as auto. swapNormalAndOpposite also covers a paragraph whose script direction disagrees with its layout direction.
Android <TextInput> — different path, same conclusion: "left" -> if (isRTL) Gravity.RIGHT else Gravity.LEFT.
iOS — RCTTextAttributes.effectiveParagraphStyle:
if (_layoutDirection == UIUserInterfaceLayoutDirectionRightToLeft) {
if (alignment == NSTextAlignmentRight) {
alignment = NSTextAlignmentLeft;
} else if (alignment == NSTextAlignmentLeft) {
alignment = NSTextAlignmentRight;
}
}_layoutDirection comes from YGNodeLayoutGetDirection in ParagraphShadowNode::getContent.
Closing the two obvious objections: TextAlignment really is Natural | Left | Center | Right | Justified, so an alias is the only route short of an RN change — and neither swap is gated by I18nManager.swapLeftAndRightInRTL, which has exactly two consumers, both layout (L597, L717).
So start -> left / end -> right is exact, RTL included — not an LTR default. (The docs list the accepted values but not this behaviour.)
Two suggestions:
- Put this in a code comment. The natural "RTL fix" someone sends later is
I18nManager.isRTL ? "left" : "right"forend— wrong on both platforms: under RTL it yieldsleft, which Android resolves toALIGN_NORMALand iOS flips back toRight, both visually start. - An RTL test asserting
start≡leftandend≡rightwould make that fail loudly instead of silently inverting.
Aside: auto is in the allowed set but unreachable — it is not valid CSS, so lightningcss never delivers it as a bare string.
i did the 2nd suggestion, kindly check |
|
Ran this on a device (RN 0.86, Android 10, Mi MIX 3). The mapping behaves as expected, and one caveat came out of it that is worth documenting. Case handling needs nothing, and that is worth a comment. lightningcss normalises the parsed Worth noting because #391's The caveat: <View className="text-end">
<Text>123 KB</Text>
</View>Web inherits
Matching left edges is start alignment; matching right edges is end alignment. The class alone does not get there — flex alignment on the wrapper does. That is RN behaviour and not something this PR should fix. But it does mean anyone applying One test suggestion: a negative case pinning that the mapping does not over-match, e.g. Minor and pre-existing: |
- Hoist allowed Set to module scope (no per-call realloc) - Comment lightningcss case normalization and nativewind#391 asymmetry - Add negative test: match-parent drops with warning (no over-match) - Document native View->Text inheritance caveat in README
done |
YevheniiKotyrlo
left a comment
There was a problem hiding this comment.
Validated locally, since CI has not run on this branch.
yarn jest src/__tests__/vendor/tailwind/typography.test.tsx— 101/101 pass, including all five new cases.yarn typecheck— clean.- Mutation-tested the new tests to confirm they are load-bearing rather than decorative:
- making the mapping over-match (any unknown value returns
left) fails 3 tests, including the newmatch-parentguard; - inverting
endtoleft— the exact "RTL fix" your comment warns against — failstext-endand thetext-end is RTL-safeidentity test.
- making the mapping over-match (any unknown value returns
One caveat for anyone repeating this: run with --no-cache. With a warm jest cache the inverted-end mutation showed only one failure and the identity test appeared to pass, which reads as a vacuous test. It is not — --no-cache fails both.
Looks good to me. One naming nit inline.
| } | ||
| } | ||
|
|
||
| const TEXT_ALIGN_ALLOWED = new Set([ |
There was a problem hiding this comment.
Nit, non-blocking: the three Sets already at module scope in this file are camelCase — unparsedRuntimeParsing (L68), allowAutoProperties (L2652), namedColors (L3364). TEXT_ALIGN_ALLOWED is the first SCREAMING_SNAKE one, so the file ends up carrying both conventions.
textAlignKeywords (or allowedTextAlign) would keep it uniform. Worth settling now — the other nine parse functions in this file still build their allow-list Set per call, and whenever those get hoisted they will follow whichever convention is here.
Summary
Tailwind CSS v4 generates
text-align: startandtext-align: endfor thetext-startandtext-endutility classes. These are CSS logical values that depend on writing direction.React Native's
textAlignprop does not supportstartorendvalues — onlyauto,left,right,center, andjustify. Previously, these values were silently dropped (returnedundefinedwith a warning).Fix
start→ mapped toleftend→ mapped torightThis matches the LTR default behavior and is a pragmatic fallback since React Native doesn't support direction-aware text alignment.
Files Changed
src/compiler/declarations.tsstart→leftandend→rightmapping inparseTextAlignsrc/__tests__/vendor/tailwind/typography.test.tsxtext-startandtext-endtestsTesting
yarn test: 1053 passed (1051 existing + 2 new)yarn lint: cleanyarn typecheck: cleanChecklist