Skip to content

Commit 9d9d637

Browse files
NickGerlemanfacebook-github-bot
authored andcommitted
Wire native CSS parsing for transformOrigin
Summary: Gate `processTransformOrigin` behind `enableNativeCSSParsing()`. When the flag is on, CSS transform-origin strings like `"top left"` or `"10px 50%"` are parsed natively using the existing CSS transform-origin parser instead of being preprocessed in JS. Changelog: [Internal] Differential Revision: D94052730
1 parent 770763e commit 9d9d637

3 files changed

Lines changed: 139 additions & 2 deletions

File tree

packages/react-native/Libraries/Components/View/ReactNativeStyleAttributes.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ export const transformAttribute: AnyAttributeType = nativeCSSParsing
6161
? true
6262
: {process: processTransform};
6363

64+
export const transformOriginAttribute: AnyAttributeType = nativeCSSParsing
65+
? true
66+
: {process: processTransformOrigin};
67+
6468
const ReactNativeStyleAttributes: {[string]: AnyAttributeType, ...} = {
6569
/**
6670
* Layout
@@ -155,7 +159,7 @@ const ReactNativeStyleAttributes: {[string]: AnyAttributeType, ...} = {
155159
* Transform
156160
*/
157161
transform: transformAttribute,
158-
transformOrigin: {process: processTransformOrigin},
162+
transformOrigin: transformOriginAttribute,
159163

160164
/**
161165
* Filter

packages/react-native/ReactCommon/react/renderer/components/view/conversions.h

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -986,7 +986,8 @@ inline void fromRawValue(const PropsParserContext &context, const RawValue &valu
986986
}
987987
}
988988

989-
inline void fromRawValue(const PropsParserContext &context, const RawValue &value, TransformOrigin &result)
989+
inline void
990+
parseProcessedTransformOrigin(const PropsParserContext & /*context*/, const RawValue &value, TransformOrigin &result)
990991
{
991992
if (!value.hasType<std::vector<RawValue>>()) {
992993
result = {};
@@ -1020,6 +1021,55 @@ inline void fromRawValue(const PropsParserContext &context, const RawValue &valu
10201021
result = transformOrigin;
10211022
}
10221023

1024+
inline void parseUnprocessedTransformOriginString(const std::string &value, TransformOrigin &result)
1025+
{
1026+
auto cssOrigin = parseCSSProperty<CSSTransformOrigin>(value);
1027+
if (!std::holds_alternative<CSSTransformOrigin>(cssOrigin)) {
1028+
result = {};
1029+
return;
1030+
}
1031+
1032+
const auto &origin = std::get<CSSTransformOrigin>(cssOrigin);
1033+
TransformOrigin transformOrigin;
1034+
1035+
auto x = cssLengthPercentageToValueUnit(origin.x);
1036+
auto y = cssLengthPercentageToValueUnit(origin.y);
1037+
if (!x || !y) {
1038+
result = {};
1039+
return;
1040+
}
1041+
1042+
transformOrigin.xy[0] = x;
1043+
transformOrigin.xy[1] = y;
1044+
1045+
if (origin.z.unit != CSSLengthUnit::Px) {
1046+
result = {};
1047+
return;
1048+
}
1049+
transformOrigin.z = origin.z.value;
1050+
1051+
result = transformOrigin;
1052+
}
1053+
1054+
inline void
1055+
parseUnprocessedTransformOrigin(const PropsParserContext &context, const RawValue &value, TransformOrigin &result)
1056+
{
1057+
if (value.hasType<std::string>()) {
1058+
parseUnprocessedTransformOriginString((std::string)value, result);
1059+
} else {
1060+
parseProcessedTransformOrigin(context, value, result);
1061+
}
1062+
}
1063+
1064+
inline void fromRawValue(const PropsParserContext &context, const RawValue &value, TransformOrigin &result)
1065+
{
1066+
if (ReactNativeFeatureFlags::enableNativeCSSParsing()) {
1067+
parseUnprocessedTransformOrigin(context, value, result);
1068+
} else {
1069+
parseProcessedTransformOrigin(context, value, result);
1070+
}
1071+
}
1072+
10231073
inline void fromRawValue(const PropsParserContext &context, const RawValue &value, PointerEventsMode &result)
10241074
{
10251075
result = PointerEventsMode::Auto;

packages/react-native/ReactCommon/react/renderer/components/view/tests/ConversionsTest.cpp

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,4 +357,87 @@ TEST(ConversionsTest, unprocessed_transform_rawvalue_translate_percent) {
357357
EXPECT_EQ(result.operations[0].x.unit, UnitType::Percent);
358358
}
359359

360+
TEST(ConversionsTest, unprocessed_transform_origin_css_top_left) {
361+
TransformOrigin result;
362+
parseUnprocessedTransformOriginString("top left", result);
363+
364+
EXPECT_EQ(result.xy[0].value, 0.0f);
365+
EXPECT_EQ(result.xy[0].unit, UnitType::Percent);
366+
EXPECT_EQ(result.xy[1].value, 0.0f);
367+
EXPECT_EQ(result.xy[1].unit, UnitType::Percent);
368+
EXPECT_EQ(result.z, 0.0f);
369+
}
370+
371+
TEST(ConversionsTest, unprocessed_transform_origin_css_center) {
372+
TransformOrigin result;
373+
parseUnprocessedTransformOriginString("center", result);
374+
375+
EXPECT_EQ(result.xy[0].value, 50.0f);
376+
EXPECT_EQ(result.xy[0].unit, UnitType::Percent);
377+
EXPECT_EQ(result.xy[1].value, 50.0f);
378+
EXPECT_EQ(result.xy[1].unit, UnitType::Percent);
379+
EXPECT_EQ(result.z, 0.0f);
380+
}
381+
382+
TEST(ConversionsTest, unprocessed_transform_origin_css_right_bottom) {
383+
TransformOrigin result;
384+
parseUnprocessedTransformOriginString("right bottom", result);
385+
386+
EXPECT_EQ(result.xy[0].value, 100.0f);
387+
EXPECT_EQ(result.xy[0].unit, UnitType::Percent);
388+
EXPECT_EQ(result.xy[1].value, 100.0f);
389+
EXPECT_EQ(result.xy[1].unit, UnitType::Percent);
390+
EXPECT_EQ(result.z, 0.0f);
391+
}
392+
393+
TEST(ConversionsTest, unprocessed_transform_origin_css_length_percent) {
394+
TransformOrigin result;
395+
parseUnprocessedTransformOriginString("10px 50%", result);
396+
397+
EXPECT_EQ(result.xy[0].value, 10.0f);
398+
EXPECT_EQ(result.xy[0].unit, UnitType::Point);
399+
EXPECT_EQ(result.xy[1].value, 50.0f);
400+
EXPECT_EQ(result.xy[1].unit, UnitType::Percent);
401+
EXPECT_EQ(result.z, 0.0f);
402+
}
403+
404+
TEST(ConversionsTest, unprocessed_transform_origin_processed_array) {
405+
RawValue value{folly::dynamic::array("50%", "50%", 0)};
406+
407+
TransformOrigin result;
408+
parseProcessedTransformOrigin(
409+
PropsParserContext{-1, ContextContainer{}}, value, result);
410+
411+
EXPECT_EQ(result.xy[0].value, 50.0f);
412+
EXPECT_EQ(result.xy[0].unit, UnitType::Percent);
413+
EXPECT_EQ(result.xy[1].value, 50.0f);
414+
EXPECT_EQ(result.xy[1].unit, UnitType::Percent);
415+
EXPECT_EQ(result.z, 0.0f);
416+
}
417+
418+
TEST(ConversionsTest, unprocessed_transform_origin_rawvalue_string) {
419+
RawValue value{folly::dynamic("top left")};
420+
TransformOrigin result;
421+
parseUnprocessedTransformOrigin(
422+
PropsParserContext{-1, ContextContainer{}}, value, result);
423+
424+
EXPECT_EQ(result.xy[0].value, 0.0f);
425+
EXPECT_EQ(result.xy[0].unit, UnitType::Percent);
426+
EXPECT_EQ(result.xy[1].value, 0.0f);
427+
EXPECT_EQ(result.xy[1].unit, UnitType::Percent);
428+
}
429+
430+
TEST(ConversionsTest, unprocessed_transform_origin_rawvalue_array) {
431+
RawValue value{folly::dynamic::array(10, "50%", 5)};
432+
TransformOrigin result;
433+
parseUnprocessedTransformOrigin(
434+
PropsParserContext{-1, ContextContainer{}}, value, result);
435+
436+
EXPECT_EQ(result.xy[0].value, 10.0f);
437+
EXPECT_EQ(result.xy[0].unit, UnitType::Point);
438+
EXPECT_EQ(result.xy[1].value, 50.0f);
439+
EXPECT_EQ(result.xy[1].unit, UnitType::Percent);
440+
EXPECT_EQ(result.z, 5.0f);
441+
}
442+
360443
} // namespace facebook::react

0 commit comments

Comments
 (0)