feat: flex gap bindings (#34974)

Summary:
This PR adds React native binding for https://github.com/facebook/yoga/pull/1116

## Changelog

[General] [Added] - Flex gap yoga bindings

<!-- Help reviewers and the release process by writing your own changelog entry. For an example, see:
https://reactnative.dev/contributing/changelogs-in-pull-requests
-->

Pull Request resolved: https://github.com/facebook/react-native/pull/34974

Test Plan:
Run rn tester app and go to view example. You'll find a flex gap example. Example location - `packages/rn-tester/js/examples/View/ViewExample.js`

### Tested on
- [x] iOS Fabric
- [x] iOS non-fabric
- [x] Android Fabric
- [x] Android non-fabric

To test on non-fabric Android, I just switched these booleans. Let me know if there's anything else I might have missed.

<img width="674" alt="Screenshot 2022-10-14 at 3 30 48 AM" src="https://user-images.githubusercontent.com/23293248/195718971-7aee4e7e-dbf0-4452-9d47-7925919c61dc.png">

Reviewed By: mdvacca

Differential Revision: D40527474

Pulled By: NickGerleman

fbshipit-source-id: 81c2c97c76f58fad3bb40fb378aaf8b6ebd30d63
This commit is contained in:
Nishan
2022-10-20 14:53:32 -07:00
committed by Facebook GitHub Bot
parent a68c418082
commit 9f3a3e13cc
17 changed files with 186 additions and 0 deletions
@@ -32,6 +32,7 @@ const ReactNativeStyleAttributes: {[string]: AnyAttributeType, ...} = {
borderRightWidth: true,
borderStartWidth: true,
borderTopWidth: true,
columnGap: true,
borderWidth: true,
bottom: true,
direction: true,
@@ -43,6 +44,7 @@ const ReactNativeStyleAttributes: {[string]: AnyAttributeType, ...} = {
flexGrow: true,
flexShrink: true,
flexWrap: true,
gap: true,
height: true,
justifyContent: true,
left: true,
@@ -71,6 +73,7 @@ const ReactNativeStyleAttributes: {[string]: AnyAttributeType, ...} = {
paddingVertical: true,
position: true,
right: true,
rowGap: true,
start: true,
top: true,
width: true,
@@ -196,6 +196,9 @@ const validAttributesForNonEventProps = {
maxHeight: true,
flex: true,
flexGrow: true,
rowGap: true,
columnGap: true,
gap: true,
flexShrink: true,
flexBasis: true,
aspectRatio: true,
@@ -271,6 +271,9 @@ const validAttributesForNonEventProps = {
flex: true,
flexGrow: true,
rowGap: true,
columnGap: true,
gap: true,
flexShrink: true,
flexBasis: true,
flexDirection: true,
+3
View File
@@ -52,6 +52,9 @@ export interface FlexStyle {
| 'row-reverse'
| 'column-reverse'
| undefined;
rowGap?: number | undefined;
gap?: number | undefined;
columnGap?: number | undefined;
flexGrow?: number | undefined;
flexShrink?: number | undefined;
flexWrap?: 'wrap' | 'nowrap' | 'wrap-reverse' | undefined;
+13
View File
@@ -518,6 +518,19 @@ export type ____ShadowStyle_InternalCore = $ReadOnly<{
* @platform ios
*/
shadowRadius?: number,
/**
* In React Native, gap works the same way it does in CSS.
* If there are two or more children in a container, they will be separated from each other
* by the value of the gap - but the children will not be separated from the edges of their parent container.
* For horizontal gaps, use columnGap, for vertical gaps, use rowGap, and to apply both at the same time, it's gap.
* When align-content or justify-content are set to space-between or space-around, the separation
* between children may be larger than the gap value.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/gap for more details.
*/
rowGap?: number,
columnGap?: number,
gap?: number,
}>;
export type ____ShadowStyle_Internal = $ReadOnly<{
+3
View File
@@ -49,6 +49,9 @@ export default function splitLayoutProps(props: ?____ViewStyle_Internal): {
case 'bottom':
case 'top':
case 'transform':
case 'rowGap':
case 'columnGap':
case 'gap':
// $FlowFixMe[cannot-write]
// $FlowFixMe[incompatible-use]
// $FlowFixMe[prop-missing]
+3
View File
@@ -152,6 +152,9 @@ typedef void (^RCTApplierBlock)(NSDictionary<NSNumber *, UIView *> *viewRegistry
@property (nonatomic, assign) float flex;
@property (nonatomic, assign) float flexGrow;
@property (nonatomic, assign) float rowGap;
@property (nonatomic, assign) float columnGap;
@property (nonatomic, assign) float gap;
@property (nonatomic, assign) float flexShrink;
@property (nonatomic, assign) YGValue flexBasis;
+14
View File
@@ -642,6 +642,20 @@ RCTShadowViewMeasure(YGNodeRef node, float width, YGMeasureMode widthMode, float
return YGNodeStyleGetFlexBasis(_yogaNode);
}
#define RCT_GAP_PROPERTY(setProp, getProp, cssProp, type, gap) \
-(void)set##setProp : (type)value \
{ \
YGNodeStyleSet##cssProp(_yogaNode, gap, value); \
} \
-(type)getProp \
{ \
return YGNodeStyleGet##cssProp(_yogaNode, gap); \
}
RCT_GAP_PROPERTY(RowGap, rowGap, Gap, float, YGGutterRow);
RCT_GAP_PROPERTY(ColumnGap, columnGap, Gap, float, YGGutterColumn);
RCT_GAP_PROPERTY(Gap, gap, Gap, float, YGGutterAll);
#define RCT_STYLE_PROPERTY(setProp, getProp, cssProp, type) \
-(void)set##setProp : (type)value \
{ \
+3
View File
@@ -426,6 +426,9 @@ RCT_EXPORT_SHADOW_PROPERTY(alignSelf, YGAlign)
RCT_EXPORT_SHADOW_PROPERTY(alignContent, YGAlign)
RCT_EXPORT_SHADOW_PROPERTY(position, YGPositionType)
RCT_EXPORT_SHADOW_PROPERTY(aspectRatio, float)
RCT_EXPORT_SHADOW_PROPERTY(rowGap, float)
RCT_EXPORT_SHADOW_PROPERTY(columnGap, float)
RCT_EXPORT_SHADOW_PROPERTY(gap, float)
RCT_EXPORT_SHADOW_PROPERTY(overflow, YGOverflow)
RCT_EXPORT_SHADOW_PROPERTY(display, YGDisplay)
@@ -224,6 +224,30 @@ public class LayoutShadowNode extends ReactShadowNodeImpl {
super.setFlexGrow(flexGrow);
}
@ReactProp(name = ViewProps.ROW_GAP, defaultFloat = YogaConstants.UNDEFINED)
public void setRowGap(float rowGap) {
if (isVirtual()) {
return;
}
super.setRowGap(PixelUtil.toPixelFromDIP(rowGap));
}
@ReactProp(name = ViewProps.COLUMN_GAP, defaultFloat = YogaConstants.UNDEFINED)
public void setColumnGap(float columnGap) {
if (isVirtual()) {
return;
}
super.setColumnGap(PixelUtil.toPixelFromDIP(columnGap));
}
@ReactProp(name = ViewProps.GAP, defaultFloat = YogaConstants.UNDEFINED)
public void setGap(float gap) {
if (isVirtual()) {
return;
}
super.setGap(PixelUtil.toPixelFromDIP(gap));
}
@ReactProp(name = ViewProps.FLEX_SHRINK, defaultFloat = 0f)
public void setFlexShrink(float flexShrink) {
if (isVirtual()) {
@@ -308,6 +308,12 @@ public interface ReactShadowNode<T extends ReactShadowNode> {
void setFlexGrow(float flexGrow);
void setRowGap(float rowGap);
void setColumnGap(float columnGap);
void setGap(float gap);
void setFlexShrink(float flexShrink);
void setFlexBasis(float flexBasis);
@@ -18,6 +18,7 @@ import com.facebook.yoga.YogaDirection;
import com.facebook.yoga.YogaDisplay;
import com.facebook.yoga.YogaEdge;
import com.facebook.yoga.YogaFlexDirection;
import com.facebook.yoga.YogaGutter;
import com.facebook.yoga.YogaJustify;
import com.facebook.yoga.YogaMeasureFunction;
import com.facebook.yoga.YogaNode;
@@ -794,6 +795,21 @@ public class ReactShadowNodeImpl implements ReactShadowNode<ReactShadowNodeImpl>
mYogaNode.setFlexGrow(flexGrow);
}
@Override
public void setRowGap(float rowGap) {
mYogaNode.setGap(YogaGutter.ROW, rowGap);
}
@Override
public void setColumnGap(float columnGap) {
mYogaNode.setGap(YogaGutter.COLUMN, columnGap);
}
@Override
public void setGap(float gap) {
mYogaNode.setGap(YogaGutter.ALL, gap);
}
@Override
public void setFlexShrink(float flexShrink) {
mYogaNode.setFlexShrink(flexShrink);
@@ -32,6 +32,9 @@ public class ViewProps {
public static final String FLEX_BASIS = "flexBasis";
public static final String FLEX_DIRECTION = "flexDirection";
public static final String FLEX_WRAP = "flexWrap";
public static final String ROW_GAP = "rowGap";
public static final String COLUMN_GAP = "columnGap";
public static final String GAP = "gap";
public static final String HEIGHT = "height";
public static final String JUSTIFY_CONTENT = "justifyContent";
public static final String LEFT = "left";
@@ -203,6 +206,9 @@ public class ViewProps {
FLEX_BASIS,
FLEX_DIRECTION,
FLEX_GROW,
ROW_GAP,
COLUMN_GAP,
GAP,
FLEX_SHRINK,
FLEX_WRAP,
JUSTIFY_CONTENT,
@@ -65,6 +65,11 @@ static inline T const getFieldValue(
REBUILD_YG_FIELD_SWITCH_CASE_INDEXED(field, YGDimensionWidth, widthStr); \
REBUILD_YG_FIELD_SWITCH_CASE_INDEXED(field, YGDimensionHeight, heightStr);
#define REBUILD_FIELD_YG_GUTTER(field, rowGapStr, columnGapStr, gapStr) \
REBUILD_YG_FIELD_SWITCH_CASE_INDEXED(field, YGGutterRow, rowGapStr); \
REBUILD_YG_FIELD_SWITCH_CASE_INDEXED(field, YGGutterColumn, columnGapStr); \
REBUILD_YG_FIELD_SWITCH_CASE_INDEXED(field, YGGutterAll, gapStr);
#define REBUILD_FIELD_YG_EDGES(field, prefix, suffix) \
REBUILD_YG_FIELD_SWITCH_CASE_INDEXED( \
field, YGEdgeLeft, prefix "Left" suffix); \
@@ -114,6 +119,7 @@ void YogaStylableProps::setProp(
REBUILD_FIELD_SWITCH_CASE_YSP(flexShrink);
REBUILD_FIELD_SWITCH_CASE_YSP(flexBasis);
REBUILD_FIELD_SWITCH_CASE2(positionType, "position");
REBUILD_FIELD_YG_GUTTER(gap, "rowGap", "columnGap", "gap");
REBUILD_FIELD_SWITCH_CASE_YSP(aspectRatio);
REBUILD_FIELD_YG_DIMENSION(dimensions, "width", "height");
REBUILD_FIELD_YG_DIMENSION(minDimensions, "minWidth", "minHeight");
@@ -163,6 +169,18 @@ SharedDebugStringConvertibleList YogaStylableProps::getDebugProps() const {
"flex", yogaStyle.flex(), defaultYogaStyle.flex()),
debugStringConvertibleItem(
"flexGrow", yogaStyle.flexGrow(), defaultYogaStyle.flexGrow()),
debugStringConvertibleItem(
"rowGap",
yogaStyle.gap()[YGGutterRow],
defaultYogaStyle.gap()[YGGutterRow]),
debugStringConvertibleItem(
"columnGap",
yogaStyle.gap()[YGGutterColumn],
defaultYogaStyle.gap()[YGGutterColumn]),
debugStringConvertibleItem(
"gap",
yogaStyle.gap()[YGGutterAll],
defaultYogaStyle.gap()[YGGutterAll]),
debugStringConvertibleItem(
"flexShrink", yogaStyle.flexShrink(), defaultYogaStyle.flexShrink()),
debugStringConvertibleItem(
@@ -266,6 +266,28 @@ static inline YGStyle convertRawProp(
"",
sourceValue.padding(),
yogaStyle.padding());
yogaStyle.gap()[YGGutterRow] = convertRawProp(
context,
rawProps,
"rowGap",
sourceValue.gap()[YGGutterRow],
yogaStyle.gap()[YGGutterRow]);
yogaStyle.gap()[YGGutterColumn] = convertRawProp(
context,
rawProps,
"columnGap",
sourceValue.gap()[YGGutterColumn],
yogaStyle.gap()[YGGutterColumn]);
yogaStyle.gap()[YGGutterAll] = convertRawProp(
context,
rawProps,
"gap",
sourceValue.gap()[YGGutterAll],
yogaStyle.gap()[YGGutterAll]);
yogaStyle.border() = convertRawProp(
context,
rawProps,
@@ -8,6 +8,7 @@
#pragma once
#include <glog/logging.h>
#include <gtest/gtest.h>
#include <algorithm>
#include <iostream>
#include <memory>
@@ -233,6 +234,11 @@ static inline ShadowNode::Unshared messWithYogaStyles(
"maxWidth", "maxHeight", "minWidth", "minHeight",
};
// It is not safe to add new Yoga properties to this list. Unit tests
// validate specific seeds, and what they test may change and cause unrelated
// failures if the size of properties also changes.
EXPECT_EQ(properties.size(), 20);
for (auto const &property : properties) {
if (entropy.random<bool>(0.1)) {
dynamic[property] = entropy.random<int>(0, 1024);
@@ -299,6 +299,40 @@ class DisplayNoneStyle extends React.Component<
this.setState({index: this.state.index + 1});
};
}
class FlexGapExample extends React.Component<$ReadOnly<{||}>> {
render(): React.Node {
return (
<View
style={{
flexDirection: 'row',
flexWrap: 'wrap',
borderWidth: 1,
rowGap: 20,
columnGap: 30,
}}>
<View style={{backgroundColor: 'black', height: 30, width: 30}} />
<View style={{backgroundColor: 'black', height: 30, width: 30}} />
<View
style={{
backgroundColor: 'pink',
height: 30,
flexBasis: 30,
}}
/>
<View style={{backgroundColor: 'black', height: 30, width: 30}} />
<View style={{backgroundColor: 'black', height: 30, width: 30}} />
<View style={{backgroundColor: 'black', height: 30, width: 30}} />
<View style={{backgroundColor: 'black', height: 30, width: 30}} />
<View style={{backgroundColor: 'pink', height: 30, width: 30}} />
<View style={{backgroundColor: 'pink', height: 30, width: 30}} />
<View style={{backgroundColor: 'pink', height: 30, width: 30}} />
<View style={{backgroundColor: 'pink', height: 30, width: 30}} />
</View>
);
}
}
exports.title = 'View';
exports.documentationURL = 'https://reactnative.dev/docs/view';
exports.category = 'Basic';
@@ -612,4 +646,10 @@ exports.examples = [
);
},
},
{
title: 'FlexGap',
render(): React.Node {
return <FlexGapExample />;
},
},
];