Skip to content

Commit 0a9b6be

Browse files
Emil SjolanderFacebook Github Bot 6
Emil Sjolander
authored and
Facebook Github Bot 6
committed
BREAKING - Fix unconstraint sizing in main axis
Summary: This fixes measuring of items in the main axis of a container. Previously items were in a lot of cases measured with UNSPECIFIED instead of AT_MOST. This was to support scrolling containers. The correct way to handle scrolling containers is to instead provide them with their own overflow value to activate this behavior. This is also similar to how the web works. This is a breaking change. Most of your layouts will continue to function as before however some of them might not. Typically this is due to having a `flex: 1` style where it is currently a no-op due to being measured with an undefined size but after this change it may collapse your component to take zero size due to the implicit `flexBasis: 0` now being correctly treated. Removing the bad `flex: 1` style or changing it to `flexGrow: 1` should solve most if not all layout issues your see after this diff. Reviewed By: majak Differential Revision: D3876927 fbshipit-source-id: 81ea1c9d6574dd4564a3333f1b3617cf84b4022f
1 parent 295ee16 commit 0a9b6be

File tree

15 files changed

+90
-36
lines changed

15 files changed

+90
-36
lines changed

Libraries/Components/ScrollView/ScrollView.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -529,7 +529,7 @@ const ScrollView = React.createClass({
529529
return React.cloneElement(
530530
refreshControl,
531531
{style: props.style},
532-
<ScrollViewClass {...props} style={baseStyle} ref={this._setScrollViewRef}>
532+
<ScrollViewClass {...props} ref={this._setScrollViewRef}>
533533
{contentContainer}
534534
</ScrollViewClass>
535535
);
@@ -545,12 +545,14 @@ const ScrollView = React.createClass({
545545

546546
const styles = StyleSheet.create({
547547
baseVertical: {
548-
flex: 1,
548+
flexGrow: 1,
549549
flexDirection: 'column',
550+
overflow: 'scroll',
550551
},
551552
baseHorizontal: {
552-
flex: 1,
553+
flexGrow: 1,
553554
flexDirection: 'row',
555+
overflow: 'scroll',
554556
},
555557
contentContainerHorizontal: {
556558
flexDirection: 'row',

Libraries/Components/View/ViewStylePropTypes.js

-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ var ViewStylePropTypes = {
4343
borderBottomWidth: ReactPropTypes.number,
4444
borderLeftWidth: ReactPropTypes.number,
4545
opacity: ReactPropTypes.number,
46-
overflow: ReactPropTypes.oneOf(['visible', 'hidden']),
4746
/**
4847
* (Android-only) Sets the elevation of a view, using Android's underlying
4948
* [elevation API](https://developer.android.com/training/material/shadows-clipping.html#Elevation).

Libraries/StyleSheet/LayoutPropTypes.js

+13
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,19 @@ var LayoutPropTypes = {
328328
'stretch'
329329
]),
330330

331+
/** `overflow` controls how a children are measured and displayed.
332+
* `overflow: hidden` causes views to be clipped while `overflow: scroll`
333+
* causes views to be measured independently of their parents main axis.`
334+
* It works like `overflow` in CSS (default: visible).
335+
* See https://developer.mozilla.org/en/docs/Web/CSS/overflow
336+
* for more details.
337+
*/
338+
overflow: ReactPropTypes.oneOf([
339+
'visible',
340+
'hidden',
341+
'scroll',
342+
]),
343+
331344
/** In React Native `flex` does not work the same way that it does in CSS.
332345
* `flex` is a number rather than a string, and it works
333346
* according to the `css-layout` library

React/Base/RCTConvert.m

+2-1
Original file line numberDiff line numberDiff line change
@@ -612,7 +612,8 @@ + (NSPropertyList)NSPropertyList:(id)json
612612

613613
RCT_ENUM_CONVERTER(CSSOverflow, (@{
614614
@"hidden": @(CSSOverflowHidden),
615-
@"visible": @(CSSOverflowVisible)
615+
@"visible": @(CSSOverflowVisible),
616+
@"scroll": @(CSSOverflowScroll),
616617
}), CSSOverflowVisible, intValue)
617618

618619
RCT_ENUM_CONVERTER(CSSFlexDirection, (@{

React/CSSLayout/CSSLayout.c

+19-15
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ void CSSNodeInit(const CSSNodeRef node) {
183183
// Such that the comparison is always going to be false
184184
node->layout.lastParentDirection = (CSSDirection) -1;
185185
node->layout.nextCachedMeasurementsIndex = 0;
186+
node->layout.computedFlexBasis = CSSUndefined;
186187

187188
node->layout.measuredDimensions[CSSDimensionWidth] = CSSUndefined;
188189
node->layout.measuredDimensions[CSSDimensionHeight] = CSSUndefined;
@@ -193,6 +194,7 @@ void CSSNodeInit(const CSSNodeRef node) {
193194
void _CSSNodeMarkDirty(const CSSNodeRef node) {
194195
if (!node->isDirty) {
195196
node->isDirty = true;
197+
node->layout.computedFlexBasis = CSSUndefined;
196198
if (node->parent) {
197199
_CSSNodeMarkDirty(node->parent);
198200
}
@@ -451,6 +453,8 @@ _CSSNodePrint(const CSSNodeRef node, const CSSPrintOptions options, const uint32
451453
printf("overflow: 'hidden', ");
452454
} else if (node->style.overflow == CSSOverflowVisible) {
453455
printf("overflow: 'visible', ");
456+
} else if (node->style.overflow == CSSOverflowScroll) {
457+
printf("overflow: 'scroll', ");
454458
}
455459

456460
if (eqFour(node->style.margin)) {
@@ -1117,8 +1121,10 @@ static void layoutNodeImpl(const CSSNodeRef node,
11171121
getPaddingAndBorderAxis(child, CSSFlexDirectionColumn));
11181122
} else if (!CSSValueIsUndefined(child->style.flexBasis) &&
11191123
!CSSValueIsUndefined(availableInnerMainDim)) {
1120-
child->layout.computedFlexBasis =
1121-
fmaxf(child->style.flexBasis, getPaddingAndBorderAxis(child, mainAxis));
1124+
if (CSSValueIsUndefined(child->layout.computedFlexBasis)) {
1125+
child->layout.computedFlexBasis =
1126+
fmaxf(child->style.flexBasis, getPaddingAndBorderAxis(child, mainAxis));
1127+
}
11221128
} else {
11231129
// Compute the flex basis and hypothetical main size (i.e. the clamped
11241130
// flex basis).
@@ -1138,21 +1144,19 @@ static void layoutNodeImpl(const CSSNodeRef node,
11381144
childHeightMeasureMode = CSSMeasureModeExactly;
11391145
}
11401146

1141-
// According to the spec, if the main size is not definite and the
1142-
// child's inline axis is parallel to the main axis (i.e. it's
1143-
// horizontal), the child should be sized using "UNDEFINED" in
1144-
// the main size. Otherwise use "AT_MOST" in the cross axis.
1145-
if (!isMainAxisRow && CSSValueIsUndefined(childWidth) &&
1146-
!CSSValueIsUndefined(availableInnerWidth)) {
1147-
childWidth = availableInnerWidth;
1148-
childWidthMeasureMode = CSSMeasureModeAtMost;
1149-
}
1150-
11511147
// The W3C spec doesn't say anything about the 'overflow' property,
11521148
// but all major browsers appear to implement the following logic.
1153-
if (node->style.overflow == CSSOverflowHidden) {
1154-
if (isMainAxisRow && CSSValueIsUndefined(childHeight) &&
1155-
!CSSValueIsUndefined(availableInnerHeight)) {
1149+
if ((!isMainAxisRow && node->style.overflow == CSSOverflowScroll) ||
1150+
node->style.overflow != CSSOverflowScroll) {
1151+
if (CSSValueIsUndefined(childWidth) && !CSSValueIsUndefined(availableInnerWidth)) {
1152+
childWidth = availableInnerWidth;
1153+
childWidthMeasureMode = CSSMeasureModeAtMost;
1154+
}
1155+
}
1156+
1157+
if ((isMainAxisRow && node->style.overflow == CSSOverflowScroll) ||
1158+
node->style.overflow != CSSOverflowScroll) {
1159+
if (CSSValueIsUndefined(childHeight) && !CSSValueIsUndefined(availableInnerHeight)) {
11561160
childHeight = availableInnerHeight;
11571161
childHeightMeasureMode = CSSMeasureModeAtMost;
11581162
}

React/CSSLayout/CSSLayout.h

+1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ typedef enum CSSJustify {
5555
typedef enum CSSOverflow {
5656
CSSOverflowVisible,
5757
CSSOverflowHidden,
58+
CSSOverflowScroll,
5859
} CSSOverflow;
5960

6061
// Note: auto is only a valid value for alignSelf. It is NOT a valid value for

React/Views/RCTScrollViewManager.m

+10
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
#import "RCTBridge.h"
1313
#import "RCTScrollView.h"
14+
#import "RCTShadowView.h"
1415
#import "RCTUIManager.h"
1516

1617
@interface RCTScrollView (Private)
@@ -79,6 +80,15 @@ - (UIView *)view
7980
RCT_EXPORT_VIEW_PROPERTY(onMomentumScrollEnd, RCTDirectEventBlock)
8081
RCT_EXPORT_VIEW_PROPERTY(onScrollAnimationEnd, RCTDirectEventBlock)
8182

83+
// overflow is used both in css-layout as well as by reac-native. In css-layout
84+
// we always want to treat overflow as scroll but depending on what the overflow
85+
// is set to from js we want to clip drawing or not. This piece of code ensures
86+
// that css-layout is always treating the contents of a scroll container as
87+
// overflow: 'scroll'.
88+
RCT_CUSTOM_SHADOW_PROPERTY(overflow, CSSOverflow, RCTShadowView) {
89+
view.overflow = CSSOverflowScroll;
90+
}
91+
8292
RCT_EXPORT_METHOD(getContentSize:(nonnull NSNumber *)reactTag
8393
callback:(RCTResponseSenderBlock)callback)
8494
{

React/Views/RCTViewManager.h

+15
Original file line numberDiff line numberDiff line change
@@ -117,4 +117,19 @@ RCT_REMAP_VIEW_PROPERTY(name, __custom__, type) \
117117
#define RCT_EXPORT_SHADOW_PROPERTY(name, type) \
118118
+ (NSArray<NSString *> *)propConfigShadow_##name { return @[@#type]; }
119119

120+
/**
121+
* This macro maps a named property to an arbitrary key path in the shadow view.
122+
*/
123+
#define RCT_REMAP_SHADOW_PROPERTY(name, keyPath, type) \
124+
+ (NSArray<NSString *> *)propConfigShadow_##name { return @[@#type, @#keyPath]; }
125+
126+
/**
127+
* This macro can be used when you need to provide custom logic for setting
128+
* shadow view properties. The macro should be followed by a method body, which can
129+
* refer to "json", "view" and "defaultView" to implement the required logic.
130+
*/
131+
#define RCT_CUSTOM_SHADOW_PROPERTY(name, type, viewClass) \
132+
RCT_REMAP_SHADOW_PROPERTY(name, __custom__, type) \
133+
- (void)set_##name:(id)json forShadowView:(viewClass *)view withDefaultView:(viewClass *)defaultView
134+
120135
@end

React/Views/RCTViewManager.m

+1-1
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ - (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(__unused NSDictio
122122
RCT_CUSTOM_VIEW_PROPERTY(overflow, CSSOverflow, RCTView)
123123
{
124124
if (json) {
125-
view.clipsToBounds = [RCTConvert CSSOverflow:json] == CSSOverflowHidden;
125+
view.clipsToBounds = [RCTConvert CSSOverflow:json] != CSSOverflowVisible;
126126
} else {
127127
view.clipsToBounds = defaultView.clipsToBounds;
128128
}

ReactAndroid/src/main/java/com/facebook/csslayout/CSSLayout.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public void resetResult() {
5151
Arrays.fill(dimensions, CSSConstants.UNDEFINED);
5252
direction = CSSDirection.LTR;
5353

54-
computedFlexBasis = 0;
54+
computedFlexBasis = CSSConstants.UNDEFINED;
5555

5656
generationCount = 0;
5757
lastParentDirection = null;

ReactAndroid/src/main/java/com/facebook/csslayout/CSSNode.java

+1
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ public void dirty() {
181181
}
182182

183183
mLayoutState = LayoutState.DIRTY;
184+
layout.computedFlexBasis = CSSConstants.UNDEFINED;
184185

185186
if (mParent != null) {
186187
mParent.dirty();

ReactAndroid/src/main/java/com/facebook/csslayout/CSSOverflow.java

+1
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@
1212
public enum CSSOverflow {
1313
VISIBLE,
1414
HIDDEN,
15+
SCROLL,
1516
}

ReactAndroid/src/main/java/com/facebook/csslayout/LayoutEngine.java

+12-14
Original file line numberDiff line numberDiff line change
@@ -695,9 +695,9 @@ private static void layoutNodeImpl(
695695
// The height is definite, so use that as the flex basis.
696696
child.layout.computedFlexBasis = Math.max(child.style.dimensions[DIMENSION_HEIGHT], ((child.style.padding.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN]) + child.style.border.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN])) + (child.style.padding.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN]) + child.style.border.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN]))));
697697
} else if (!isFlexBasisAuto(child) && !Float.isNaN(availableInnerMainDim)) {
698-
699-
// If the basis isn't 'auto', it is assumed to be zero.
700-
child.layout.computedFlexBasis = Math.max(child.style.flexBasis, ((child.style.padding.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + child.style.border.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis])) + (child.style.padding.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]) + child.style.border.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]))));
698+
if (Float.isNaN(child.layout.computedFlexBasis)) {
699+
child.layout.computedFlexBasis = Math.max(child.style.flexBasis, ((child.style.padding.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + child.style.border.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis])) + (child.style.padding.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]) + child.style.border.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]))));
700+
}
701701
} else {
702702

703703
// Compute the flex basis and hypothetical main size (i.e. the clamped flex basis).
@@ -715,19 +715,17 @@ private static void layoutNodeImpl(
715715
childHeightMeasureMode = CSSMeasureMode.EXACTLY;
716716
}
717717

718-
// According to the spec, if the main size is not definite and the
719-
// child's inline axis is parallel to the main axis (i.e. it's
720-
// horizontal), the child should be sized using "UNDEFINED" in
721-
// the main size. Otherwise use "AT_MOST" in the cross axis.
722-
if (!isMainAxisRow && Float.isNaN(childWidth) && !Float.isNaN(availableInnerWidth)) {
723-
childWidth = availableInnerWidth;
724-
childWidthMeasureMode = CSSMeasureMode.AT_MOST;
725-
}
726-
727718
// The W3C spec doesn't say anything about the 'overflow' property,
728719
// but all major browsers appear to implement the following logic.
729-
if (node.style.overflow == CSSOverflow.HIDDEN) {
730-
if (isMainAxisRow && Float.isNaN(childHeight) && !Float.isNaN(availableInnerHeight)) {
720+
if ((!isMainAxisRow && node.style.overflow == CSSOverflow.SCROLL) || node.style.overflow != CSSOverflow.SCROLL) {
721+
if (Float.isNaN(childWidth) && !Float.isNaN(availableInnerWidth)) {
722+
childWidth = availableInnerWidth;
723+
childWidthMeasureMode = CSSMeasureMode.AT_MOST;
724+
}
725+
}
726+
727+
if ((isMainAxisRow && node.style.overflow == CSSOverflow.SCROLL) || node.style.overflow != CSSOverflow.SCROLL) {
728+
if (Float.isNaN(childHeight) && !Float.isNaN(availableInnerHeight)) {
731729
childHeight = availableInnerHeight;
732730
childHeightMeasureMode = CSSMeasureMode.AT_MOST;
733731
}

ReactAndroid/src/main/java/com/facebook/react/uimanager/LayoutShadowNode.java

+7
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import com.facebook.csslayout.CSSConstants;
1111
import com.facebook.csslayout.CSSFlexDirection;
1212
import com.facebook.csslayout.CSSJustify;
13+
import com.facebook.csslayout.CSSOverflow;
1314
import com.facebook.csslayout.CSSPositionType;
1415
import com.facebook.csslayout.CSSWrap;
1516
import com.facebook.react.uimanager.annotations.ReactProp;
@@ -102,6 +103,12 @@ public void setJustifyContent(@Nullable String justifyContent) {
102103
justifyContent.toUpperCase(Locale.US).replace("-", "_")));
103104
}
104105

106+
@ReactProp(name = ViewProps.OVERFLOW)
107+
public void setOverflow(@Nullable String overflow) {
108+
setOverflow(overflow == null ? CSSOverflow.VISIBLE : CSSOverflow.valueOf(
109+
overflow.toUpperCase(Locale.US).replace("-", "_")));
110+
}
111+
105112
@ReactPropGroup(names = {
106113
ViewProps.MARGIN,
107114
ViewProps.MARGIN_VERTICAL,

ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java

+2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public class ViewProps {
2626
// !!! Keep in sync with LAYOUT_ONLY_PROPS below
2727
public static final String ALIGN_ITEMS = "alignItems";
2828
public static final String ALIGN_SELF = "alignSelf";
29+
public static final String OVERFLOW = "overflow";
2930
public static final String BOTTOM = "bottom";
3031
public static final String COLLAPSABLE = "collapsable";
3132
public static final String FLEX = "flex";
@@ -113,6 +114,7 @@ public class ViewProps {
113114
FLEX_DIRECTION,
114115
FLEX_WRAP,
115116
JUSTIFY_CONTENT,
117+
OVERFLOW,
116118

117119
/* position */
118120
POSITION,

0 commit comments

Comments
 (0)