diff --git a/packages/react-native/Libraries/Components/View/ReactNativeStyleAttributes.js b/packages/react-native/Libraries/Components/View/ReactNativeStyleAttributes.js index c40b7b84353a..b6c4e036393e 100644 --- a/packages/react-native/Libraries/Components/View/ReactNativeStyleAttributes.js +++ b/packages/react-native/Libraries/Components/View/ReactNativeStyleAttributes.js @@ -61,6 +61,10 @@ export const transformAttribute: AnyAttributeType = nativeCSSParsing ? true : {process: processTransform}; +export const transformOriginAttribute: AnyAttributeType = nativeCSSParsing + ? true + : {process: processTransformOrigin}; + const ReactNativeStyleAttributes: {[string]: AnyAttributeType, ...} = { /** * Layout @@ -155,7 +159,7 @@ const ReactNativeStyleAttributes: {[string]: AnyAttributeType, ...} = { * Transform */ transform: transformAttribute, - transformOrigin: {process: processTransformOrigin}, + transformOrigin: transformOriginAttribute, /** * Filter 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 652791b14e47..69b92b00b92b 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/conversions.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/conversions.h @@ -968,7 +968,8 @@ inline void fromRawValue(const PropsParserContext &context, const RawValue &valu } } -inline void fromRawValue(const PropsParserContext &context, const RawValue &value, TransformOrigin &result) +inline void +parseProcessedTransformOrigin(const PropsParserContext & /*context*/, const RawValue &value, TransformOrigin &result) { if (!value.hasType>()) { result = {}; @@ -1002,6 +1003,55 @@ inline void fromRawValue(const PropsParserContext &context, const RawValue &valu result = transformOrigin; } +inline void parseUnprocessedTransformOriginString(const std::string &value, TransformOrigin &result) +{ + auto cssOrigin = parseCSSProperty(value); + if (!std::holds_alternative(cssOrigin)) { + result = {}; + return; + } + + const auto &origin = std::get(cssOrigin); + TransformOrigin transformOrigin; + + auto x = cssLengthPercentageToValueUnit(origin.x); + auto y = cssLengthPercentageToValueUnit(origin.y); + if (!x || !y) { + result = {}; + return; + } + + transformOrigin.xy[0] = x; + transformOrigin.xy[1] = y; + + if (origin.z.unit != CSSLengthUnit::Px) { + result = {}; + return; + } + transformOrigin.z = origin.z.value; + + result = transformOrigin; +} + +inline void +parseUnprocessedTransformOrigin(const PropsParserContext &context, const RawValue &value, TransformOrigin &result) +{ + if (value.hasType()) { + parseUnprocessedTransformOriginString((std::string)value, result); + } else { + parseProcessedTransformOrigin(context, value, result); + } +} + +inline void fromRawValue(const PropsParserContext &context, const RawValue &value, TransformOrigin &result) +{ + if (ReactNativeFeatureFlags::enableNativeCSSParsing()) { + parseUnprocessedTransformOrigin(context, value, result); + } else { + parseProcessedTransformOrigin(context, value, result); + } +} + inline void fromRawValue(const PropsParserContext &context, const RawValue &value, PointerEventsMode &result) { result = PointerEventsMode::Auto; 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 a8d1cf19d856..c784ecfd3ed7 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 @@ -357,4 +357,101 @@ TEST(ConversionsTest, unprocessed_transform_rawvalue_translate_percent) { EXPECT_EQ(result.operations[0].x.unit, UnitType::Percent); } +TEST(ConversionsTest, unprocessed_transform_origin_css_top_left) { + TransformOrigin result; + parseUnprocessedTransformOriginString("top left", result); + + EXPECT_EQ(result.xy[0].value, 0.0f); + EXPECT_EQ(result.xy[0].unit, UnitType::Percent); + EXPECT_EQ(result.xy[1].value, 0.0f); + EXPECT_EQ(result.xy[1].unit, UnitType::Percent); + EXPECT_EQ(result.z, 0.0f); +} + +TEST(ConversionsTest, unprocessed_transform_origin_css_center) { + TransformOrigin result; + parseUnprocessedTransformOriginString("center", result); + + EXPECT_EQ(result.xy[0].value, 50.0f); + EXPECT_EQ(result.xy[0].unit, UnitType::Percent); + EXPECT_EQ(result.xy[1].value, 50.0f); + EXPECT_EQ(result.xy[1].unit, UnitType::Percent); + EXPECT_EQ(result.z, 0.0f); +} + +TEST(ConversionsTest, unprocessed_transform_origin_css_right_bottom) { + TransformOrigin result; + parseUnprocessedTransformOriginString("right bottom", result); + + EXPECT_EQ(result.xy[0].value, 100.0f); + EXPECT_EQ(result.xy[0].unit, UnitType::Percent); + EXPECT_EQ(result.xy[1].value, 100.0f); + EXPECT_EQ(result.xy[1].unit, UnitType::Percent); + EXPECT_EQ(result.z, 0.0f); +} + +TEST(ConversionsTest, unprocessed_transform_origin_css_length_percent) { + TransformOrigin result; + parseUnprocessedTransformOriginString("10px 50%", result); + + EXPECT_EQ(result.xy[0].value, 10.0f); + EXPECT_EQ(result.xy[0].unit, UnitType::Point); + EXPECT_EQ(result.xy[1].value, 50.0f); + EXPECT_EQ(result.xy[1].unit, UnitType::Percent); + EXPECT_EQ(result.z, 0.0f); +} + +TEST(ConversionsTest, unprocessed_transform_origin_processed_array) { + RawValue value{folly::dynamic::array("50%", "50%", 0)}; + + TransformOrigin result; + parseProcessedTransformOrigin( + PropsParserContext{-1, ContextContainer{}}, value, result); + + EXPECT_EQ(result.xy[0].value, 50.0f); + EXPECT_EQ(result.xy[0].unit, UnitType::Percent); + EXPECT_EQ(result.xy[1].value, 50.0f); + EXPECT_EQ(result.xy[1].unit, UnitType::Percent); + EXPECT_EQ(result.z, 0.0f); +} + +TEST(ConversionsTest, unprocessed_transform_origin_rawvalue_string) { + RawValue value{folly::dynamic("top left")}; + TransformOrigin result; + parseUnprocessedTransformOrigin( + PropsParserContext{-1, ContextContainer{}}, value, result); + + EXPECT_EQ(result.xy[0].value, 0.0f); + EXPECT_EQ(result.xy[0].unit, UnitType::Percent); + EXPECT_EQ(result.xy[1].value, 0.0f); + EXPECT_EQ(result.xy[1].unit, UnitType::Percent); + EXPECT_EQ(result.z, 0.0f); +} + +TEST(ConversionsTest, unprocessed_transform_origin_rawvalue_array) { + RawValue value{folly::dynamic::array(10, "50%", 5)}; + TransformOrigin result; + parseUnprocessedTransformOrigin( + PropsParserContext{-1, ContextContainer{}}, value, result); + + EXPECT_EQ(result.xy[0].value, 10.0f); + EXPECT_EQ(result.xy[0].unit, UnitType::Point); + EXPECT_EQ(result.xy[1].value, 50.0f); + EXPECT_EQ(result.xy[1].unit, UnitType::Percent); + EXPECT_EQ(result.z, 5.0f); +} + +TEST(ConversionsTest, unprocessed_transform_origin_rawvalue_string_with_z) { + RawValue value{folly::dynamic("center center 15px")}; + TransformOrigin result; + parseUnprocessedTransformOrigin( + PropsParserContext{-1, ContextContainer{}}, value, result); + + EXPECT_EQ(result.xy[0].value, 50.0f); + EXPECT_EQ(result.xy[0].unit, UnitType::Percent); + EXPECT_EQ(result.xy[1].value, 50.0f); + EXPECT_EQ(result.xy[1].unit, UnitType::Percent); + EXPECT_EQ(result.z, 15.0f); +} + } // namespace facebook::react