Skip to content

Commit 9c3254d

Browse files
committed
fix: accept three-value conic gradient positions
1 parent b418cf4 commit 9c3254d

2 files changed

Lines changed: 108 additions & 4 deletions

File tree

‎packages/react-native/ReactCommon/react/renderer/css/CSSBackgroundImage.h‎

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -927,9 +927,6 @@ struct CSSDataTypeParser<CSSConicGradientFunction> {
927927
if (third.has_value()) {
928928
parser.syntaxParser().consumeWhitespace();
929929
fourth = parsePositionComponent(parser);
930-
if (!fourth.has_value()) {
931-
return {};
932-
}
933930
}
934931
auto position = resolvePosition(*first, second, third, fourth);
935932
if (!position.has_value()) {
@@ -1001,6 +998,57 @@ struct CSSDataTypeParser<CSSConicGradientFunction> {
1001998
return std::get<CSSPercentage>(component);
1002999
}
10031000

1001+
// [ center | [ left | right ] <length-percentage>? ] && [ center | [ top | bottom ] <length-percentage>? ]
1002+
// with exactly one offset, which belongs to the edge keyword directly before it.
1003+
static std::optional<CSSRadialGradientPosition> resolveThreeValuePosition(
1004+
const PositionComponent &first,
1005+
const PositionComponent &second,
1006+
const PositionComponent &third)
1007+
{
1008+
const bool offsetIsSecond = !std::holds_alternative<CSSGradientPositionKeyword>(second);
1009+
if (!std::holds_alternative<CSSGradientPositionKeyword>(first) ||
1010+
offsetIsSecond == !std::holds_alternative<CSSGradientPositionKeyword>(third)) {
1011+
return {};
1012+
}
1013+
auto offsetKeyword = std::get<CSSGradientPositionKeyword>(offsetIsSecond ? first : second);
1014+
auto bareKeyword = std::get<CSSGradientPositionKeyword>(offsetIsSecond ? third : first);
1015+
auto isHorizontal = [](CSSGradientPositionKeyword keyword) {
1016+
return keyword == CSSGradientPositionKeyword::Left || keyword == CSSGradientPositionKeyword::Right;
1017+
};
1018+
if (offsetKeyword == CSSGradientPositionKeyword::Center ||
1019+
(bareKeyword != CSSGradientPositionKeyword::Center &&
1020+
isHorizontal(bareKeyword) == isHorizontal(offsetKeyword))) {
1021+
return {};
1022+
}
1023+
1024+
CSSRadialGradientPosition position;
1025+
auto setEdge = [&position](CSSGradientPositionKeyword keyword, std::variant<CSSLength, CSSPercentage> value) {
1026+
switch (keyword) {
1027+
case CSSGradientPositionKeyword::Left:
1028+
position.left = value;
1029+
break;
1030+
case CSSGradientPositionKeyword::Right:
1031+
position.right = value;
1032+
break;
1033+
case CSSGradientPositionKeyword::Top:
1034+
position.top = value;
1035+
break;
1036+
case CSSGradientPositionKeyword::Bottom:
1037+
position.bottom = value;
1038+
break;
1039+
case CSSGradientPositionKeyword::Center:
1040+
break;
1041+
}
1042+
};
1043+
setEdge(offsetKeyword, positionValue(offsetIsSecond ? second : third));
1044+
if (bareKeyword == CSSGradientPositionKeyword::Center) {
1045+
(isHorizontal(offsetKeyword) ? position.top : position.left) = CSSPercentage{50.0f};
1046+
} else {
1047+
setEdge(bareKeyword, CSSPercentage{0.0f});
1048+
}
1049+
return position;
1050+
}
1051+
10041052
static std::optional<CSSRadialGradientPosition> resolvePosition(
10051053
const PositionComponent &first,
10061054
const std::optional<PositionComponent> &second,
@@ -1009,7 +1057,13 @@ struct CSSDataTypeParser<CSSConicGradientFunction> {
10091057
{
10101058
CSSRadialGradientPosition position;
10111059
if (third.has_value()) {
1012-
if (!second.has_value() || !fourth.has_value() || !std::holds_alternative<CSSGradientPositionKeyword>(first) ||
1060+
if (!second.has_value()) {
1061+
return {};
1062+
}
1063+
if (!fourth.has_value()) {
1064+
return resolveThreeValuePosition(first, *second, *third);
1065+
}
1066+
if (!std::holds_alternative<CSSGradientPositionKeyword>(first) ||
10131067
!std::holds_alternative<CSSGradientPositionKeyword>(*third) ||
10141068
std::holds_alternative<CSSGradientPositionKeyword>(*second) ||
10151069
std::holds_alternative<CSSGradientPositionKeyword>(*fourth)) {

‎packages/react-native/ReactCommon/react/renderer/css/tests/CSSBackgroundImageTest.cpp‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -639,6 +639,56 @@ TEST_F(CSSBackgroundImageTest, ConicGradientWithFourValuePosition) {
639639
ASSERT_EQ(result, expected);
640640
}
641641

642+
TEST_F(CSSBackgroundImageTest, ConicGradientWithThreeValuePosition) {
643+
const std::vector<std::pair<std::string, CSSRadialGradientPosition>>
644+
testCases = {
645+
{"conic-gradient(at right 10px top, red, blue)",
646+
{.top = CSSPercentage{.value = 0.0f},
647+
.right = CSSLength{.value = 10.0f, .unit = CSSLengthUnit::Px}}},
648+
{"conic-gradient(at top 20px left, red, blue)",
649+
{.top = CSSLength{.value = 20.0f, .unit = CSSLengthUnit::Px},
650+
.left = CSSPercentage{.value = 0.0f}}},
651+
{"conic-gradient(at left bottom 10%, red, blue)",
652+
{.bottom = CSSPercentage{.value = 10.0f},
653+
.left = CSSPercentage{.value = 0.0f}}},
654+
{"conic-gradient(at center top 25%, red, blue)",
655+
{.top = CSSPercentage{.value = 25.0f},
656+
.left = CSSPercentage{.value = 50.0f}}},
657+
{"conic-gradient(at right 25% center, red, blue)",
658+
{.top = CSSPercentage{.value = 50.0f},
659+
.right = CSSPercentage{.value = 25.0f}}},
660+
{"conic-gradient(at top 25% center, red, blue)",
661+
{.top = CSSPercentage{.value = 25.0f},
662+
.left = CSSPercentage{.value = 50.0f}}}};
663+
664+
for (const auto& [input, expectedPosition] : testCases) {
665+
const auto result = parseCSSProperty<CSSBackgroundImage>(input);
666+
decltype(result) expected = CSSConicGradientFunction{
667+
.from = CSSAngle{.degrees = 0.0f},
668+
.position = expectedPosition,
669+
.items = {
670+
CSSConicColorStop{
671+
.color = CSSColor{.r = 255, .g = 0, .b = 0, .a = 255}},
672+
CSSConicColorStop{
673+
.color = CSSColor{.r = 0, .g = 0, .b = 255, .a = 255}}}};
674+
ASSERT_EQ(result, expected) << "Failed for input: " << input;
675+
}
676+
}
677+
678+
TEST_F(CSSBackgroundImageTest, ConicGradientRejectsInvalidThreeValuePosition) {
679+
for (const auto& input : {
680+
"conic-gradient(at left top center, red, blue)",
681+
"conic-gradient(at center 10px top, red, blue)",
682+
"conic-gradient(at 10px left top, red, blue)",
683+
"conic-gradient(at left 10px right, red, blue)",
684+
"conic-gradient(at left 10px 20px, red, blue)",
685+
}) {
686+
const auto result = parseCSSProperty<CSSBackgroundImage>(input);
687+
ASSERT_TRUE(std::holds_alternative<std::monostate>(result))
688+
<< "Unexpectedly accepted: " << input;
689+
}
690+
}
691+
642692
TEST_F(CSSBackgroundImageTest, InvalidGradientFunctionName) {
643693
const std::string input =
644694
"aoeusntial-gradient(red 0%, yellow 30%, green 60%, blue 100%)";

0 commit comments

Comments
 (0)