From e818a22290e1a0739938654949452604aef9ea91 Mon Sep 17 00:00:00 2001 From: Yevhenii Date: Mon, 12 Oct 2026 07:51:29 +0300 Subject: [PATCH 1/2] Implement fadingEdgeLength for ScrollView on iOS ScrollView declares fadingEdgeLength on its cross-platform props, but only Android implements it. The iOS view config does not list it and the cxx HostPlatformScrollViewProps has no field for it, so iOS drops the value. RCTScrollViewComponentView masks the inner UIScrollView's layer with a CAGradientLayer in which each edge fades over its own length at full strength, which is what Android draws for {start, end}. The iOS view config forwards fadingEdgeLength and horizontal, so the mask follows the declared axis, and the prop moves to the shared props in Flow, the TypeScript types and the API snapshot. --- .../Components/ScrollView/ScrollView.js | 28 +++-- .../ScrollView/ScrollViewNativeComponent.js | 2 + .../ScrollView/RCTScrollViewComponentView.mm | 88 +++++++++++++++ .../RCTScrollViewComponentViewTests.mm | 102 ++++++++++++++++++ .../HostPlatformScrollViewProps.cpp | 41 +++++++ .../scrollview/HostPlatformScrollViewProps.h | 19 +++- packages/react-native/ReactNativeApi.d.ts | 38 +++---- .../Components/ScrollView/ScrollView.d.ts | 30 +++--- .../Libraries/Lists/FlatList.d.ts | 2 - .../js/examples/FlatList/FlatList-basic.js | 27 +++-- .../examples/ScrollView/ScrollViewExample.js | 71 +++++++++--- 11 files changed, 368 insertions(+), 80 deletions(-) create mode 100644 packages/react-native/ReactCommon/react/renderer/components/scrollview/platform/cxx/react/renderer/components/scrollview/HostPlatformScrollViewProps.cpp diff --git a/packages/react-native/Libraries/Components/ScrollView/ScrollView.js b/packages/react-native/Libraries/Components/ScrollView/ScrollView.js index 5c7974f2ac30..47691aa748ea 100644 --- a/packages/react-native/Libraries/Components/ScrollView/ScrollView.js +++ b/packages/react-native/Libraries/Components/ScrollView/ScrollView.js @@ -442,21 +442,6 @@ export type ScrollViewPropsAndroid = Readonly<{ * @platform android */ persistentScrollbar?: ?boolean, - /** - * Controls the fading effect at the edges of the scroll content. - * - * A value greater than 0 will apply the fading effect, indicating more content is available - * to scroll. - * - * You can specify a single number to apply the same fading length to both edges. - * Alternatively, use an object with `start` and `end` properties to set different - * fading lengths for the start and end of the scroll content. - * - * The default value is 0. - * - * @platform android - */ - fadingEdgeLength?: ?number | {start: number, end: number}, /** * When false, the ScrollView will not automatically scroll to a focused child when * the child requests focus. This can be useful when you want to control the scroll @@ -523,6 +508,19 @@ type ScrollViewBaseProps = Readonly<{ * @default `'normal'` */ decelerationRate?: ?DecelerationRateType, + /** + * Controls the fading effect at the edges of the scroll content. + * + * A value greater than 0 will apply the fading effect, indicating more content is available + * to scroll. + * + * You can specify a single number to apply the same fading length to both edges. + * Alternatively, use an object with `start` and `end` properties to set different + * fading lengths for the start and end of the scroll content. + * + * The default value is 0. + */ + fadingEdgeLength?: ?number | {start: number, end: number}, /** * *Experimental, iOS Only*. The API is experimental and will change in future releases. diff --git a/packages/react-native/Libraries/Components/ScrollView/ScrollViewNativeComponent.js b/packages/react-native/Libraries/Components/ScrollView/ScrollViewNativeComponent.js index eeee1a603e72..c2808ad30088 100644 --- a/packages/react-native/Libraries/Components/ScrollView/ScrollViewNativeComponent.js +++ b/packages/react-native/Libraries/Components/ScrollView/ScrollViewNativeComponent.js @@ -124,6 +124,8 @@ export const __INTERNAL_VIEW_CONFIG: PartialViewConfig = endDraggingSensitivityMultiplier: true, directionalLockEnabled: true, disableIntervalMomentum: true, + fadingEdgeLength: true, + horizontal: true, indicatorStyle: true, inverted: true, keyboardDismissMode: true, diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm index ffd10bc6f236..01772da1dbcf 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm @@ -58,6 +58,22 @@ static UIScrollViewIndicatorStyle RCTUIScrollViewIndicatorStyleFromProps(const S } } +static CGFloat RCTFadingEdgeLengthFromValue(const folly::dynamic *value) +{ + return (value != nullptr && value->isNumber()) ? fmax(value->asDouble(), 0) : 0; +} + +static void RCTGetFadingEdgeLengths(const ScrollViewProps &props, CGFloat *start, CGFloat *end) +{ + const auto &fadingEdgeLength = props.fadingEdgeLength; + if (fadingEdgeLength.isObject()) { + *start = RCTFadingEdgeLengthFromValue(fadingEdgeLength.get_ptr("start")); + *end = RCTFadingEdgeLengthFromValue(fadingEdgeLength.get_ptr("end")); + } else { + *start = *end = RCTFadingEdgeLengthFromValue(&fadingEdgeLength); + } +} + // Once Fabric implements proper NativeAnimationDriver, this should be removed. // This is just a workaround to allow animations based on onScroll event. // This is only used to animate sticky headers in ScrollViews, and only the contentOffset and tag is used. @@ -125,6 +141,8 @@ @implementation RCTScrollViewComponentView { BOOL _avoidAdjustmentForMaintainVisibleContentPosition; RCTVirtualViewContainerState *_virtualViewContainerState; + + CAGradientLayer *_fadingEdgeMaskLayer; } + (RCTScrollViewComponentView *_Nullable)findScrollViewComponentViewForView:(UIView *)view @@ -315,6 +333,7 @@ - (void)updateLayoutMetrics:(const LayoutMetrics &)layoutMetrics _containerView.transform = transform; _scrollView.transform = transform; } + [self _updateFadingEdgeMask]; } - (bool)isInverted @@ -454,10 +473,77 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & scrollView.keyboardDismissMode = RCTUIKeyboardDismissModeFromProps(newScrollViewProps); } + BOOL fadingEdgeChanged = oldScrollViewProps.fadingEdgeLength != newScrollViewProps.fadingEdgeLength || + oldScrollViewProps.horizontal != newScrollViewProps.horizontal; + [super updateProps:props oldProps:oldProps]; // Must run after `super updateProps:` so it is based on the latest calculated view. ((RCTEnhancedScrollView *)_scrollView).inverted = [self isInverted]; + + if (fadingEdgeChanged) { + [self _updateFadingEdgeMask]; + } +} + +// Android's `View.draw` geometry: each edge fades over its own length, scaled so that neither passes the middle. +- (void)_updateFadingEdgeMask +{ + const auto &props = static_cast(*_props); + CGFloat start; + CGFloat end; + RCTGetFadingEdgeLengths(props, &start, &end); + CGFloat length = fmax(start, end); + CGFloat extent = props.horizontal ? _scrollView.bounds.size.width : _scrollView.bounds.size.height; + + if (length == 0 || extent == 0) { + // A mask costs an offscreen pass, so a scroll view without a fading edge carries none. + if (_fadingEdgeMaskLayer != nil) { + _scrollView.layer.mask = nil; + _fadingEdgeMaskLayer = nil; + } + return; + } + + CGFloat fadeLength = fmin(length, extent / 2); + if (_fadingEdgeMaskLayer == nil) { + _fadingEdgeMaskLayer = [CAGradientLayer layer]; + _fadingEdgeMaskLayer.colors = @[ + (id)UIColor.clearColor.CGColor, + (id)UIColor.blackColor.CGColor, + (id)UIColor.blackColor.CGColor, + (id)UIColor.clearColor.CGColor, + ]; + } + + [CATransaction begin]; + [CATransaction setDisableActions:YES]; + // `start` and `end` are logical edges: in RTL the scroll view is mirrored, and its mask with it. + _fadingEdgeMaskLayer.startPoint = props.horizontal ? CGPointMake(0, 0.5) : CGPointMake(0.5, 0); + _fadingEdgeMaskLayer.endPoint = props.horizontal ? CGPointMake(1, 0.5) : CGPointMake(0.5, 1); + _fadingEdgeMaskLayer.locations = @[ + @0, + @(fadeLength * start / length / extent), + @(1 - fadeLength * end / length / extent), + @1, + ]; + _fadingEdgeMaskLayer.frame = _scrollView.bounds; + [CATransaction commit]; + + // The scroll view rather than this view: RCTViewComponentView owns `currentContainerView.layer.mask`. + _scrollView.layer.mask = _fadingEdgeMaskLayer; +} + +- (void)_layoutFadingEdgeMask +{ + if (_fadingEdgeMaskLayer == nil) { + return; + } + // The mask is laid out in the scroll view's bounds, whose origin is the content offset. + [CATransaction begin]; + [CATransaction setDisableActions:YES]; + _fadingEdgeMaskLayer.frame = _scrollView.bounds; + [CATransaction commit]; } - (void)updateState:(const State::Shared &)state oldState:(const State::Shared &)oldState @@ -747,6 +833,8 @@ - (BOOL)touchesShouldCancelInContentView:(__unused UIView *)view - (void)scrollViewDidScroll:(UIScrollView *)scrollView { + [self _layoutFadingEdgeMask]; + auto scrollMetrics = [self _scrollViewMetrics]; [self _updateStateWithContentOffset]; diff --git a/packages/react-native/React/Tests/Mounting/RCTScrollViewComponentViewTests.mm b/packages/react-native/React/Tests/Mounting/RCTScrollViewComponentViewTests.mm index 82b350755668..17f372136af1 100644 --- a/packages/react-native/React/Tests/Mounting/RCTScrollViewComponentViewTests.mm +++ b/packages/react-native/React/Tests/Mounting/RCTScrollViewComponentViewTests.mm @@ -9,7 +9,10 @@ #import #import #import +#import +using facebook::react::EmptyLayoutMetrics; +using facebook::react::LayoutMetrics; using facebook::react::Props; using facebook::react::ScrollViewProps; using facebook::react::ScrollViewShadowNode; @@ -23,6 +26,14 @@ return props; } +static Props::Shared makeFadingEdgeProps(folly::dynamic fadingEdgeLength, bool horizontal = false) +{ + auto props = std::make_shared(); + props->fadingEdgeLength = std::move(fadingEdgeLength); + props->horizontal = horizontal; + return props; +} + @interface RCTScrollViewComponentView (Tests) - (void)_keyboardWillChangeFrame:(NSNotification *)notification; @end @@ -61,6 +72,97 @@ - (void)testAutomaticallyAdjustKeyboardInsetsAcrossRecycling XCTAssertEqual(view.scrollView.contentInset.bottom, 50); } +- (CAGradientLayer *)fadingEdgeMaskOf:(RCTScrollViewComponentView *)view +{ + CALayer *mask = view.scrollView.layer.mask; + XCTAssertTrue([mask isKindOfClass:[CAGradientLayer class]]); + return (CAGradientLayer *)mask; +} + +- (void)assertLocations:(NSArray *)locations equal:(NSArray *)expected +{ + XCTAssertEqual(locations.count, expected.count); + for (NSUInteger index = 0; index < MIN(locations.count, expected.count); index++) { + XCTAssertEqualWithAccuracy(locations[index].doubleValue, expected[index].doubleValue, 1e-6); + } +} + +- (void)testFadingEdgeLengthFadesEachEdgeOverItsOwnLength +{ + RCTScrollViewComponentView *view = [[RCTScrollViewComponentView alloc] initWithFrame:CGRectMake(0, 0, 100, 200)]; + [view updateProps:makeFadingEdgeProps(folly::dynamic::object("start", 20)("end", 40)) + oldProps:ScrollViewShadowNode::defaultSharedProps()]; + + CAGradientLayer *mask = [self fadingEdgeMaskOf:view]; + XCTAssertTrue(CGPointEqualToPoint(mask.startPoint, CGPointMake(0.5, 0))); + XCTAssertTrue(CGPointEqualToPoint(mask.endPoint, CGPointMake(0.5, 1))); + [self assertLocations:mask.locations equal:@[ @0, @0.1, @0.8, @1 ]]; + XCTAssertTrue(CGRectEqualToRect(mask.frame, view.scrollView.bounds)); +} + +- (void)testFadingEdgeLengthNumberFadesBothEdges +{ + RCTScrollViewComponentView *view = [[RCTScrollViewComponentView alloc] initWithFrame:CGRectMake(0, 0, 100, 200)]; + [view updateProps:makeFadingEdgeProps(30) oldProps:ScrollViewShadowNode::defaultSharedProps()]; + + [self assertLocations:[self fadingEdgeMaskOf:view].locations equal:@[ @0, @0.15, @0.85, @1 ]]; +} + +- (void)testHorizontalFadingEdgeLengthFadesAlongTheWidth +{ + RCTScrollViewComponentView *view = [[RCTScrollViewComponentView alloc] initWithFrame:CGRectMake(0, 0, 100, 200)]; + [view updateProps:makeFadingEdgeProps(folly::dynamic::object("start", 20)("end", 0), true) + oldProps:ScrollViewShadowNode::defaultSharedProps()]; + + CAGradientLayer *mask = [self fadingEdgeMaskOf:view]; + XCTAssertTrue(CGPointEqualToPoint(mask.startPoint, CGPointMake(0, 0.5))); + XCTAssertTrue(CGPointEqualToPoint(mask.endPoint, CGPointMake(1, 0.5))); + [self assertLocations:mask.locations equal:@[ @0, @0.2, @1, @1 ]]; +} + +- (void)testFadingEdgesNeverOverlap +{ + RCTScrollViewComponentView *view = [[RCTScrollViewComponentView alloc] initWithFrame:CGRectMake(0, 0, 100, 200)]; + [view updateProps:makeFadingEdgeProps(folly::dynamic::object("start", 300)("end", 150)) + oldProps:ScrollViewShadowNode::defaultSharedProps()]; + + [self assertLocations:[self fadingEdgeMaskOf:view].locations equal:@[ @0, @0.5, @0.75, @1 ]]; +} + +- (void)testZeroFadingEdgeLengthRemovesTheMask +{ + RCTScrollViewComponentView *view = [[RCTScrollViewComponentView alloc] initWithFrame:CGRectMake(0, 0, 100, 200)]; + [view updateProps:makeFadingEdgeProps(40) oldProps:ScrollViewShadowNode::defaultSharedProps()]; + XCTAssertNotNil(view.scrollView.layer.mask); + + [view updateProps:makeFadingEdgeProps(0) oldProps:nullptr]; + XCTAssertNil(view.scrollView.layer.mask); +} + +- (void)testFadingEdgeMaskFollowsTheContentOffset +{ + RCTScrollViewComponentView *view = [[RCTScrollViewComponentView alloc] initWithFrame:CGRectMake(0, 0, 100, 200)]; + [view updateProps:makeFadingEdgeProps(40) oldProps:ScrollViewShadowNode::defaultSharedProps()]; + + view.scrollView.contentSize = CGSizeMake(100, 1000); + view.scrollView.contentOffset = CGPointMake(0, 300); + + XCTAssertTrue(CGRectEqualToRect([self fadingEdgeMaskOf:view].frame, CGRectMake(0, 300, 100, 200))); +} + +- (void)testFadingEdgeMaskFollowsTheScrollViewSize +{ + RCTScrollViewComponentView *view = [[RCTScrollViewComponentView alloc] initWithFrame:CGRectMake(0, 0, 100, 200)]; + [view updateProps:makeFadingEdgeProps(40) oldProps:ScrollViewShadowNode::defaultSharedProps()]; + [self assertLocations:[self fadingEdgeMaskOf:view].locations equal:@[ @0, @0.2, @0.8, @1 ]]; + + LayoutMetrics layoutMetrics = EmptyLayoutMetrics; + layoutMetrics.frame.size = {100, 400}; + [view updateLayoutMetrics:layoutMetrics oldLayoutMetrics:EmptyLayoutMetrics]; + + [self assertLocations:[self fadingEdgeMaskOf:view].locations equal:@[ @0, @0.1, @0.9, @1 ]]; +} + @end #endif diff --git a/packages/react-native/ReactCommon/react/renderer/components/scrollview/platform/cxx/react/renderer/components/scrollview/HostPlatformScrollViewProps.cpp b/packages/react-native/ReactCommon/react/renderer/components/scrollview/platform/cxx/react/renderer/components/scrollview/HostPlatformScrollViewProps.cpp new file mode 100644 index 000000000000..587f5616b661 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/components/scrollview/platform/cxx/react/renderer/components/scrollview/HostPlatformScrollViewProps.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "HostPlatformScrollViewProps.h" + +#include + +namespace facebook::react { + +HostPlatformScrollViewProps::HostPlatformScrollViewProps( + const PropsParserContext& context, + const HostPlatformScrollViewProps& sourceProps, + const RawProps& rawProps) + : BaseScrollViewProps(context, sourceProps, rawProps), + fadingEdgeLength(convertRawProp( + context, + rawProps, + "fadingEdgeLength", + sourceProps.fadingEdgeLength, + nullptr)) {} + +void HostPlatformScrollViewProps::setProp( + const PropsParserContext& context, + RawPropsPropNameHash hash, + const char* propName, + const RawValue& value) { + // All Props structs setProp methods must always, unconditionally, + // call all super::setProp methods, since multiple structs may + // reuse the same values. + BaseScrollViewProps::setProp(context, hash, propName, value); + + static auto defaults = HostPlatformScrollViewProps{}; + + switch (hash) { RAW_SET_PROP_SWITCH_CASE_BASIC(fadingEdgeLength); } +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/components/scrollview/platform/cxx/react/renderer/components/scrollview/HostPlatformScrollViewProps.h b/packages/react-native/ReactCommon/react/renderer/components/scrollview/platform/cxx/react/renderer/components/scrollview/HostPlatformScrollViewProps.h index 30929a4359c5..47424d564334 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/scrollview/platform/cxx/react/renderer/components/scrollview/HostPlatformScrollViewProps.h +++ b/packages/react-native/ReactCommon/react/renderer/components/scrollview/platform/cxx/react/renderer/components/scrollview/HostPlatformScrollViewProps.h @@ -9,8 +9,25 @@ #include +#include #include namespace facebook::react { -using HostPlatformScrollViewProps = BaseScrollViewProps; + +class HostPlatformScrollViewProps : public BaseScrollViewProps { + public: + HostPlatformScrollViewProps() = default; + HostPlatformScrollViewProps( + const PropsParserContext &context, + const HostPlatformScrollViewProps &sourceProps, + const RawProps &rawProps); + + void + setProp(const PropsParserContext &context, RawPropsPropNameHash hash, const char *propName, const RawValue &value); + +#pragma mark - Props + + folly::dynamic fadingEdgeLength{}; +}; + } // namespace facebook::react diff --git a/packages/react-native/ReactNativeApi.d.ts b/packages/react-native/ReactNativeApi.d.ts index cb67856cb4b3..d4266b402acf 100644 --- a/packages/react-native/ReactNativeApi.d.ts +++ b/packages/react-native/ReactNativeApi.d.ts @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<2ceaa8e472a53fc667994edb63ccd995>> + * @generated SignedSource<<564bb5bee47c364c4aecb358d05523fa>> * * This file was generated by scripts/js-api/build-types/index.js. */ @@ -4269,6 +4269,12 @@ declare type ScrollViewBaseProps = { readonly contentOffset?: PointProp readonly decelerationRate?: DecelerationRateType readonly disableIntervalMomentum?: boolean + readonly fadingEdgeLength?: + | (number | undefined) + | { + end: number + start: number + } readonly horizontal?: boolean readonly innerViewRef?: React.Ref readonly invertStickyHeaders?: boolean @@ -4348,12 +4354,6 @@ declare interface ScrollViewProps extends Readonly< > {} declare type ScrollViewPropsAndroid = { readonly endFillColor?: ColorValue - readonly fadingEdgeLength?: - | (number | undefined) - | { - end: number - start: number - } readonly nestedScrollEnabled?: boolean readonly overScrollMode?: "always" | "auto" | "never" readonly persistentScrollbar?: boolean @@ -5779,7 +5779,7 @@ export { AlertOptions, // 8a116d2a AlertType, // 5ab91217 AndroidKeyboardEvent, // e03becc8 - Animated, // ed4648e6 + Animated, // 3acee102 AppConfig, // 35c0ca70 AppRegistry, // edec2a30 AppState, // 12012be5 @@ -5831,9 +5831,9 @@ export { EventSubscription, // b8d084aa ExtendedExceptionData, // 5a6ccf5a FilterFunction, // bf24c0e3 - FlatList, // 76dfb3dc - FlatListInstance, // 1d6b2ce4 - FlatListProps, // 14bbe483 + FlatList, // c9d066e1 + FlatListInstance, // 92bb0df6 + FlatListProps, // 30029c49 FocusEvent, // f2e4363d FontVariant, // 7c7558bb GestureResponderEvent, // 5cd2a663 @@ -5975,18 +5975,18 @@ export { ScrollEvent, // 956fdb27 ScrollResponderType, // e41ccb2a ScrollToLocationParamsType, // d7ecdad1 - ScrollView, // 651915c2 + ScrollView, // 7b4f2a71 ScrollViewImperativeMethods, // cb73da62 ScrollViewInstance, // 099b48e2 - ScrollViewProps, // 7e38d97b - ScrollViewPropsAndroid, // 02f3df2e + ScrollViewProps, // d5685bcd + ScrollViewPropsAndroid, // e691594a ScrollViewPropsIOS, // c378379b ScrollViewScrollToOptions, // 3313411e SectionBase, // 9f13db00 - SectionList, // 542a3017 + SectionList, // 820679bc SectionListData, // 1a4de01a - SectionListInstance, // ea2f03e4 - SectionListProps, // d6282d2b + SectionListInstance, // 7454c87e + SectionListProps, // 667eca3f SectionListRenderItem, // 715b2086 SectionListRenderItemInfo, // 4a48a922 Separators, // 6a45f7e3 @@ -6061,10 +6061,10 @@ export { VirtualViewMode, // 6be59722 VirtualizedList, // 68c7345e VirtualizedListInstance, // 423ee7c0 - VirtualizedListProps, // 7a7c618a + VirtualizedListProps, // ceda6d7f VirtualizedSectionList, // 9fd9cd61 VirtualizedSectionListInstance, // 12b706d5 - VirtualizedSectionListProps, // 8373f27c + VirtualizedSectionListProps, // 4a5e66d2 WrapperComponentProvider, // 9b4247f6 codegenNativeCommands, // 322f3f4e codegenNativeComponent, // 3da62daa diff --git a/packages/react-native/types_DEPRECATED/Libraries/Components/ScrollView/ScrollView.d.ts b/packages/react-native/types_DEPRECATED/Libraries/Components/ScrollView/ScrollView.d.ts index d57d50b425e3..54f1636a9a7b 100644 --- a/packages/react-native/types_DEPRECATED/Libraries/Components/ScrollView/ScrollView.d.ts +++ b/packages/react-native/types_DEPRECATED/Libraries/Components/ScrollView/ScrollView.d.ts @@ -560,22 +560,6 @@ export interface ScrollViewPropsAndroid { */ nestedScrollEnabled?: boolean | undefined; - /** - * Controls the fading effect at the edges of the scroll content. - * - * A value greater than 0 will apply the fading effect, indicating more content is available - * to scroll. - * - * You can specify a single number to apply the same fading length to both edges. - * Alternatively, use an object with `start` and `end` properties to set different - * fading lengths for the start and end of the scroll content. - * - * The default value is 0. - * - * @platform android - */ - fadingEdgeLength?: number | {start: number; end: number} | undefined; - /** * Causes the scrollbars not to turn transparent when they are not in use. The default value is false. */ @@ -635,6 +619,20 @@ export interface ScrollViewProps */ decelerationRate?: 'fast' | 'normal' | number | undefined; + /** + * Controls the fading effect at the edges of the scroll content. + * + * A value greater than 0 will apply the fading effect, indicating more content is available + * to scroll. + * + * You can specify a single number to apply the same fading length to both edges. + * Alternatively, use an object with `start` and `end` properties to set different + * fading lengths for the start and end of the scroll content. + * + * The default value is 0. + */ + fadingEdgeLength?: number | {start: number; end: number} | undefined; + /** * When true the scroll view's children are arranged horizontally in a row * instead of vertically in a column. The default value is false. diff --git a/packages/react-native/types_DEPRECATED/Libraries/Lists/FlatList.d.ts b/packages/react-native/types_DEPRECATED/Libraries/Lists/FlatList.d.ts index 0914329f7833..a6e75f49eb37 100644 --- a/packages/react-native/types_DEPRECATED/Libraries/Lists/FlatList.d.ts +++ b/packages/react-native/types_DEPRECATED/Libraries/Lists/FlatList.d.ts @@ -157,8 +157,6 @@ export interface FlatListProps extends VirtualizedListProps { * fading lengths for the start and end of the scroll content. * * The default value is 0. - * - * @platform android */ fadingEdgeLength?: number | {start: number; end: number} | undefined; diff --git a/packages/rn-tester/js/examples/FlatList/FlatList-basic.js b/packages/rn-tester/js/examples/FlatList/FlatList-basic.js index cdcef788d216..cb4239aa1cdd 100644 --- a/packages/rn-tester/js/examples/FlatList/FlatList-basic.js +++ b/packages/rn-tester/js/examples/FlatList/FlatList-basic.js @@ -36,7 +36,6 @@ import { Alert, Animated, I18nManager, - Platform, StyleSheet, TextInput, View, @@ -227,20 +226,18 @@ class FlatListExample extends React.PureComponent { this.state.maintainVisibleContentPosition, this._setBooleanValue('maintainVisibleContentPosition'), )} - {Platform.OS === 'android' && ( - - - this.setState({ - fadingEdgeLength: Number(event.nativeEvent.text), - }) - } - /> - - )} + + + this.setState({ + fadingEdgeLength: Number(event.nativeEvent.text), + }) + } + /> + diff --git a/packages/rn-tester/js/examples/ScrollView/ScrollViewExample.js b/packages/rn-tester/js/examples/ScrollView/ScrollViewExample.js index ef937f99089c..4906870f9845 100644 --- a/packages/rn-tester/js/examples/ScrollView/ScrollViewExample.js +++ b/packages/rn-tester/js/examples/ScrollView/ScrollViewExample.js @@ -470,6 +470,15 @@ const examples: Array = [ return ; }, }, + { + name: 'fadingEdgeLength', + title: ' fadingEdgeLength\n', + description: + 'Fades the content out at the edges of the scroll view, with one length for both edges or one per edge.', + render(): React.Node { + return ; + }, + }, ]; if (Platform.OS === 'ios') { @@ -533,10 +542,10 @@ if (Platform.OS === 'ios') { }); } else if (Platform.OS === 'android') { examples.push({ - title: ' EndFillColor & FadingEdgeLength\n', - description: 'Toggle to set endFillColor and fadingEdgeLength.', + title: ' EndFillColor\n', + description: 'Toggle to set endFillColor.', render(): React.Node { - return ; + return ; }, }); examples.push({ @@ -653,15 +662,13 @@ const HorizontalScrollView = (props: { ); }; -const EndFillColorFadingEdgeLen = () => { +const EndFillColorExample = () => { const [endFillColor, setEndFillColor] = useState(''); - const [fadingEdgeLen, setFadingEdgeLen] = useState(0); return ( {ITEMS.map(createItemRow)} @@ -671,16 +678,42 @@ const EndFillColorFadingEdgeLen = () => { endFillColor === '' ? setEndFillColor('#A9DFD0') : setEndFillColor('') } /> -