Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,22 @@ export const transformOriginAttribute: AnyAttributeType = nativeCSSParsing
? true
: {process: processTransformOrigin};

export const fontVariantAttribute: AnyAttributeType = nativeCSSParsing
? true
: {process: processFontVariant};

export const aspectRatioAttribute: AnyAttributeType = nativeCSSParsing
? true
: {process: processAspectRatio};

const ReactNativeStyleAttributes: {[string]: AnyAttributeType, ...} = {
/**
* Layout
*/
alignContent: true,
alignItems: true,
alignSelf: true,
aspectRatio: {process: processAspectRatio},
aspectRatio: aspectRatioAttribute,
borderBottomWidth: true,
borderEndWidth: true,
borderLeftWidth: true,
Expand Down Expand Up @@ -248,7 +256,7 @@ const ReactNativeStyleAttributes: {[string]: AnyAttributeType, ...} = {
fontFamily: true,
fontSize: true,
fontStyle: true,
fontVariant: {process: processFontVariant},
fontVariant: fontVariantAttribute,
fontWeight: true,
includeFontPadding: true,
letterSpacing: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,101 @@ describe('<View>', () => {
});
});

describe('aspectRatio', () => {
it('is preserved when updating an unrelated prop', () => {
const root = Fantom.createRoot();

Fantom.runTask(() => {
root.render(
<View
style={{width: 100, aspectRatio: 2}}
nativeID="first"
collapsable={false}
/>,
);
});

// width=100, aspectRatio=2 → height = 100 / 2 = 50
expect(
root
.getRenderedOutput({
includeLayoutMetrics: true,
props: ['layoutMetrics-frame'],
})
.toJSX(),
).toEqual(
<rn-view layoutMetrics-frame="{x:0,y:0,width:100,height:50}" />,
);

// Update only nativeID, not aspectRatio
Fantom.runTask(() => {
root.render(
<View
style={{width: 100, aspectRatio: 2}}
nativeID="second"
collapsable={false}
/>,
);
});

// aspectRatio must still be preserved → same layout
expect(
root
.getRenderedOutput({
includeLayoutMetrics: true,
props: ['layoutMetrics-frame'],
})
.toJSX(),
).toEqual(
<rn-view layoutMetrics-frame="{x:0,y:0,width:100,height:50}" />,
);
});

it('can be changed to undefined after initially having a value', () => {
const root = Fantom.createRoot();

Fantom.runTask(() => {
root.render(
<View style={{width: 100, aspectRatio: 2}} collapsable={false} />,
);
});

// width=100, aspectRatio=2 → height = 100 / 2 = 50
expect(
root
.getRenderedOutput({
includeLayoutMetrics: true,
props: ['layoutMetrics-frame'],
})
.toJSX(),
).toEqual(
<rn-view layoutMetrics-frame="{x:0,y:0,width:100,height:50}" />,
);

// Update aspectRatio to undefined
Fantom.runTask(() => {
root.render(
<View
style={{width: 100, aspectRatio: undefined}}
collapsable={false}
/>,
);
});

// aspectRatio is now undefined → height collapses to 0
expect(
root
.getRenderedOutput({
includeLayoutMetrics: true,
props: ['layoutMetrics-frame'],
})
.toJSX(),
).toEqual(
<rn-view layoutMetrics-frame="{x:0,y:0,width:100,height:0}" />,
);
});
});

describe('background-image', () => {
it('it parses CSS and object syntax', () => {
const root = Fantom.createRoot();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#include <folly/dynamic.h>
#include <react/debug/react_native_expect.h>
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <react/renderer/attributedstring/AttributedString.h>
#include <react/renderer/attributedstring/ParagraphAttributes.h>
#include <react/renderer/attributedstring/TextAttributes.h>
Expand All @@ -21,6 +22,8 @@
#include <react/renderer/core/conversions.h>
#include <react/renderer/core/graphicsConversions.h>
#include <react/renderer/core/propsConversions.h>
#include <react/renderer/css/CSSFontVariant.h>
#include <react/renderer/css/CSSValueParser.h>
#include <unordered_map>

#ifdef RN_SERIALIZABLE_STATE
Expand Down Expand Up @@ -317,7 +320,73 @@ inline std::string toString(const FontStyle &fontStyle)
return "normal";
}

inline void fromRawValue(const PropsParserContext &context, const RawValue &value, FontVariant &result)
inline std::optional<FontVariant> fontVariantFromCSSFontVariant(CSSFontVariant cssVariant)
{
switch (cssVariant) {
case CSSFontVariant::SmallCaps:
return FontVariant::SmallCaps;
case CSSFontVariant::OldstyleNums:
return FontVariant::OldstyleNums;
case CSSFontVariant::LiningNums:
return FontVariant::LiningNums;
case CSSFontVariant::TabularNums:
return FontVariant::TabularNums;
case CSSFontVariant::ProportionalNums:
return FontVariant::ProportionalNums;
case CSSFontVariant::StylisticOne:
return FontVariant::StylisticOne;
case CSSFontVariant::StylisticTwo:
return FontVariant::StylisticTwo;
case CSSFontVariant::StylisticThree:
return FontVariant::StylisticThree;
case CSSFontVariant::StylisticFour:
return FontVariant::StylisticFour;
case CSSFontVariant::StylisticFive:
return FontVariant::StylisticFive;
case CSSFontVariant::StylisticSix:
return FontVariant::StylisticSix;
case CSSFontVariant::StylisticSeven:
return FontVariant::StylisticSeven;
case CSSFontVariant::StylisticEight:
return FontVariant::StylisticEight;
case CSSFontVariant::StylisticNine:
return FontVariant::StylisticNine;
case CSSFontVariant::StylisticTen:
return FontVariant::StylisticTen;
case CSSFontVariant::StylisticEleven:
return FontVariant::StylisticEleven;
case CSSFontVariant::StylisticTwelve:
return FontVariant::StylisticTwelve;
case CSSFontVariant::StylisticThirteen:
return FontVariant::StylisticThirteen;
case CSSFontVariant::StylisticFourteen:
return FontVariant::StylisticFourteen;
case CSSFontVariant::StylisticFifteen:
return FontVariant::StylisticFifteen;
case CSSFontVariant::StylisticSixteen:
return FontVariant::StylisticSixteen;
case CSSFontVariant::StylisticSeventeen:
return FontVariant::StylisticSeventeen;
case CSSFontVariant::StylisticEighteen:
return FontVariant::StylisticEighteen;
case CSSFontVariant::StylisticNineteen:
return FontVariant::StylisticNineteen;
case CSSFontVariant::StylisticTwenty:
return FontVariant::StylisticTwenty;
case CSSFontVariant::CommonLigatures:
case CSSFontVariant::NoCommonLigatures:
case CSSFontVariant::DiscretionaryLigatures:
case CSSFontVariant::NoDiscretionaryLigatures:
case CSSFontVariant::HistoricalLigatures:
case CSSFontVariant::NoHistoricalLigatures:
case CSSFontVariant::Contextual:
case CSSFontVariant::NoContextual:
return std::nullopt;
}
}

inline void
parseProcessedFontVariant(const PropsParserContext & /*context*/, const RawValue &value, FontVariant &result)
{
result = FontVariant::Default;
react_native_expect(value.hasType<std::vector<std::string>>());
Expand Down Expand Up @@ -376,14 +445,47 @@ inline void fromRawValue(const PropsParserContext &context, const RawValue &valu
result = (FontVariant)((int)result | (int)FontVariant::StylisticTwenty);
} else {
LOG(ERROR) << "Unsupported FontVariant value: " << item;
react_native_expect(false);
}
}
} else {
LOG(ERROR) << "Unsupported FontVariant type";
}
}

inline void parseUnprocessedFontVariantString(const std::string &value, FontVariant &result)
{
auto fontVariantList = parseCSSProperty<CSSFontVariantList>(value);
if (!std::holds_alternative<CSSFontVariantList>(fontVariantList)) {
result = FontVariant::Default;
return;
}

result = FontVariant::Default;
for (const auto &cssVariant : std::get<CSSFontVariantList>(fontVariantList)) {
if (auto fv = fontVariantFromCSSFontVariant(cssVariant)) {
result = (FontVariant)((int)result | (int)*fv);
}
}
}

inline void parseUnprocessedFontVariant(const PropsParserContext &context, const RawValue &value, FontVariant &result)
{
if (value.hasType<std::string>()) {
parseUnprocessedFontVariantString((std::string)value, result);
} else {
parseProcessedFontVariant(context, value, result);
}
}

inline void fromRawValue(const PropsParserContext &context, const RawValue &value, FontVariant &result)
{
if (ReactNativeFeatureFlags::enableNativeCSSParsing()) {
parseUnprocessedFontVariant(context, value, result);
} else {
parseProcessedFontVariant(context, value, result);
}
}

inline std::string toString(const FontVariant &fontVariant)
{
auto result = std::string{};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,35 +147,40 @@ void YogaStylableProps::setProp(
REBUILD_FIELD_SWITCH_CASE_YSP(flexBasis, setFlexBasis);
REBUILD_FIELD_SWITCH_CASE2(positionType, setPositionType, "position");
REBUILD_FIELD_YG_GUTTER(gap, setGap, "rowGap", "columnGap", "gap");
REBUILD_FIELD_SWITCH_CASE_YSP(aspectRatio, setAspectRatio);
REBUILD_FIELD_SWITCH_CASE_YSP(boxSizing, setBoxSizing);
REBUILD_FIELD_YG_DIMENSION(dimension, setDimension, "width", "height");
REBUILD_FIELD_YG_DIMENSION(
minDimension, setMinDimension, "minWidth", "minHeight");
REBUILD_FIELD_YG_DIMENSION(
maxDimension, setMaxDimension, "maxWidth", "maxHeight");
REBUILD_FIELD_YG_EDGES_POSITION();
REBUILD_FIELD_YG_EDGES(margin, setMargin, "margin", "");
REBUILD_FIELD_YG_EDGES(padding, setPadding, "padding", "");
REBUILD_FIELD_YG_EDGES(border, setBorder, "border", "Width");
case CONSTEXPR_RAW_PROPS_KEY_HASH("aspectRatio"): {
yogaStyle.setAspectRatio(
value.hasValue() ? convertAspectRatio(context, value)
: ygDefaults.aspectRatio());
return;
}
REBUILD_FIELD_SWITCH_CASE_YSP(boxSizing, setBoxSizing);
REBUILD_FIELD_YG_DIMENSION(dimension, setDimension, "width", "height");
REBUILD_FIELD_YG_DIMENSION(
minDimension, setMinDimension, "minWidth", "minHeight");
REBUILD_FIELD_YG_DIMENSION(
maxDimension, setMaxDimension, "maxWidth", "maxHeight");
REBUILD_FIELD_YG_EDGES_POSITION();
REBUILD_FIELD_YG_EDGES(margin, setMargin, "margin", "");
REBUILD_FIELD_YG_EDGES(padding, setPadding, "padding", "");
REBUILD_FIELD_YG_EDGES(border, setBorder, "border", "Width");

// Aliases
RAW_SET_PROP_SWITCH_CASE(insetBlockEnd, "insetBlockEnd");
RAW_SET_PROP_SWITCH_CASE(insetBlockStart, "insetBlockStart");
RAW_SET_PROP_SWITCH_CASE(insetInlineEnd, "insetInlineEnd");
RAW_SET_PROP_SWITCH_CASE(insetInlineStart, "insetInlineStart");
RAW_SET_PROP_SWITCH_CASE(marginInline, "marginInline");
RAW_SET_PROP_SWITCH_CASE(marginInlineStart, "marginInlineStart");
RAW_SET_PROP_SWITCH_CASE(marginInlineEnd, "marginInlineEnd");
RAW_SET_PROP_SWITCH_CASE(marginBlock, "marginBlock");
RAW_SET_PROP_SWITCH_CASE(marginBlockStart, "marginBlockStart");
RAW_SET_PROP_SWITCH_CASE(marginBlockEnd, "marginBlockEnd");
RAW_SET_PROP_SWITCH_CASE(paddingInline, "paddingInline");
RAW_SET_PROP_SWITCH_CASE(paddingInlineStart, "paddingInlineStart");
RAW_SET_PROP_SWITCH_CASE(paddingInlineEnd, "paddingInlineEnd");
RAW_SET_PROP_SWITCH_CASE(paddingBlock, "paddingBlock");
RAW_SET_PROP_SWITCH_CASE(paddingBlockStart, "paddingBlockStart");
RAW_SET_PROP_SWITCH_CASE(paddingBlockEnd, "paddingBlockEnd");
// Aliases
RAW_SET_PROP_SWITCH_CASE(insetBlockEnd, "insetBlockEnd");
RAW_SET_PROP_SWITCH_CASE(insetBlockStart, "insetBlockStart");
RAW_SET_PROP_SWITCH_CASE(insetInlineEnd, "insetInlineEnd");
RAW_SET_PROP_SWITCH_CASE(insetInlineStart, "insetInlineStart");
RAW_SET_PROP_SWITCH_CASE(marginInline, "marginInline");
RAW_SET_PROP_SWITCH_CASE(marginInlineStart, "marginInlineStart");
RAW_SET_PROP_SWITCH_CASE(marginInlineEnd, "marginInlineEnd");
RAW_SET_PROP_SWITCH_CASE(marginBlock, "marginBlock");
RAW_SET_PROP_SWITCH_CASE(marginBlockStart, "marginBlockStart");
RAW_SET_PROP_SWITCH_CASE(marginBlockEnd, "marginBlockEnd");
RAW_SET_PROP_SWITCH_CASE(paddingInline, "paddingInline");
RAW_SET_PROP_SWITCH_CASE(paddingInlineStart, "paddingInlineStart");
RAW_SET_PROP_SWITCH_CASE(paddingInlineEnd, "paddingInlineEnd");
RAW_SET_PROP_SWITCH_CASE(paddingBlock, "paddingBlock");
RAW_SET_PROP_SWITCH_CASE(paddingBlockStart, "paddingBlockStart");
RAW_SET_PROP_SWITCH_CASE(paddingBlockEnd, "paddingBlockEnd");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,23 @@ inline void fromRawValue(const PropsParserContext &context, const RawValue &valu
result = value.hasType<float>() ? yoga::FloatOptional((float)value) : yoga::FloatOptional();
}

inline yoga::FloatOptional convertAspectRatio(const PropsParserContext & /*context*/, const RawValue &value)
{
if (value.hasType<float>()) {
return yoga::FloatOptional((float)value);
}
if (ReactNativeFeatureFlags::enableNativeCSSParsing() && value.hasType<std::string>()) {
auto ratio = parseCSSProperty<CSSRatio>((std::string)value);
if (std::holds_alternative<CSSRatio>(ratio)) {
auto r = std::get<CSSRatio>(ratio);
if (!r.isDegenerate()) {
return yoga::FloatOptional(r.numerator / r.denominator);
}
}
}
return {};
}

inline std::optional<Float> toRadians(const RawValue &value)
{
if (value.hasType<Float>()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -356,8 +356,14 @@ convertRawProp(const PropsParserContext &context, const RawProps &rawProps, cons
yoga::Dimension::Height,
convertRawProp(context, rawProps, "maxHeight", sourceValue.maxDimension(yoga::Dimension::Height), {}));

yogaStyle.setAspectRatio(
convertRawProp(context, rawProps, "aspectRatio", sourceValue.aspectRatio(), yogaStyle.aspectRatio()));
{
const auto *rawValue = rawProps.at("aspectRatio", nullptr, nullptr);
if (rawValue != nullptr) {
yogaStyle.setAspectRatio(rawValue->hasValue() ? convertAspectRatio(context, *rawValue) : yogaStyle.aspectRatio());
} else {
yogaStyle.setAspectRatio(sourceValue.aspectRatio());
}
}

yogaStyle.setBoxSizing(
convertRawProp(context, rawProps, "boxSizing", sourceValue.boxSizing(), yogaStyle.boxSizing()));
Expand Down
Loading
Loading