From 863d102f6819b39283d67ebc02ad5585542a91bb Mon Sep 17 00:00:00 2001 From: Jacek Pudysz Date: Fri, 13 Nov 2026 12:29:27 +0100 Subject: [PATCH] Honor right and bottom backgroundPosition offsets on iOS BackgroundPosition defaults top and left to 0, and the props parser only filled in bottom and right next to them. iOS reads top and left first, so `backgroundPosition: 'right 8px bottom 8px'` drew the image at the top left. Drop the default top when the value sets bottom, and the default left when it sets right. An explicit top or left still wins, as on Android. --- .../renderer/components/view/conversions.h | 6 +++++ .../components/view/tests/ConversionsTest.cpp | 26 +++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/conversions.h b/packages/react-native/ReactCommon/react/renderer/components/view/conversions.h index 36716ef74d50..a16cc650834f 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/conversions.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/conversions.h @@ -879,6 +879,9 @@ fromRawValue(const PropsParserContext & /*context*/, const RawValue &value, std: auto valueUnit = toValueUnit(bottomIt->second); if (valueUnit) { backgroundPosition.bottom = valueUnit; + if (topIt == positionMap.end()) { + backgroundPosition.top.reset(); + } } } @@ -895,6 +898,9 @@ fromRawValue(const PropsParserContext & /*context*/, const RawValue &value, std: auto valueUnit = toValueUnit(rightIt->second); if (valueUnit) { backgroundPosition.right = valueUnit; + if (leftIt == positionMap.end()) { + backgroundPosition.left.reset(); + } } } diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/tests/ConversionsTest.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/tests/ConversionsTest.cpp index 1ca47172f9fa..9ec276b3f40c 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/tests/ConversionsTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/tests/ConversionsTest.cpp @@ -674,4 +674,30 @@ TEST(ConversionsTest, float_optional_undefined_for_string) { EXPECT_TRUE(result.isUndefined()); } +TEST(ConversionsTest, background_position_bottom_right) { + RawValue value{folly::dynamic::array( + folly::dynamic::object("bottom", 8)("right", "10%"))}; + std::vector result; + fromRawValue(PropsParserContext{-1, ContextContainer{}}, value, result); + + ASSERT_EQ(result.size(), 1); + EXPECT_FALSE(result[0].top.has_value()); + EXPECT_FALSE(result[0].left.has_value()); + EXPECT_EQ(result[0].bottom, (ValueUnit{8.0f, UnitType::Point})); + EXPECT_EQ(result[0].right, (ValueUnit{10.0f, UnitType::Percent})); +} + +TEST(ConversionsTest, background_position_top_left_next_to_bottom_right) { + RawValue value{folly::dynamic::array( + folly::dynamic::object("top", 4)("left", 2)("bottom", 8)("right", 6))}; + std::vector result; + fromRawValue(PropsParserContext{-1, ContextContainer{}}, value, result); + + ASSERT_EQ(result.size(), 1); + EXPECT_EQ(result[0].top, (ValueUnit{4.0f, UnitType::Point})); + EXPECT_EQ(result[0].left, (ValueUnit{2.0f, UnitType::Point})); + EXPECT_EQ(result[0].bottom, (ValueUnit{8.0f, UnitType::Point})); + EXPECT_EQ(result[0].right, (ValueUnit{6.0f, UnitType::Point})); +} + } // namespace facebook::react