Skip to content
Draft
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 @@ -526,6 +526,50 @@ export interface ScrollViewPropsIOS {
| ((event: NativeSyntheticEvent<NativeScrollEvent>) => void)
| undefined;

/**
* The style of the top edge effect. Available on iOS 26.0 and later.
* @platform ios
*/
topEdgeEffect?:
| {
style?: 'automatic' | 'soft' | 'hard' | undefined;
hidden?: boolean | undefined;
}
| undefined;

/**
* The style of the bottom edge effect. Available on iOS 26.0 and later.
* @platform ios
*/
bottomEdgeEffect?:
| {
style?: 'automatic' | 'soft' | 'hard' | undefined;
hidden?: boolean | undefined;
}
| undefined;

/**
* The style of the left edge effect. Available on iOS 26.0 and later.
* @platform ios
*/
leftEdgeEffect?:
| {
style?: 'automatic' | 'soft' | 'hard' | undefined;
hidden?: boolean | undefined;
}
| undefined;

/**
* The style of the right edge effect. Available on iOS 26.0 and later.
* @platform ios
*/
rightEdgeEffect?:
| {
style?: 'automatic' | 'soft' | 'hard' | undefined;
hidden?: boolean | undefined;
}
| undefined;

/**
* The current scale of the scroll view content. The default value is 1.0.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,10 @@ export const __INTERNAL_VIEW_CONFIG: PartialViewConfig =
snapToInterval: true,
snapToOffsets: true,
snapToStart: true,
topEdgeEffect: true,
bottomEdgeEffect: true,
leftEdgeEffect: true,
rightEdgeEffect: true,
verticalScrollIndicatorInsets: {
diff: require('../../Utilities/differ/insetsDiffer').default,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,22 @@ export type ScrollViewNativeProps = $ReadOnly<{
snapToInterval?: ?number,
snapToOffsets?: ?$ReadOnlyArray<number>,
snapToStart?: ?boolean,
topEdgeEffect?: ?$ReadOnly<{
style?: ?('automatic' | 'soft' | 'hard'),
hidden?: ?boolean,
}>,
bottomEdgeEffect?: ?$ReadOnly<{
style?: ?('automatic' | 'soft' | 'hard'),
hidden?: ?boolean,
}>,
leftEdgeEffect?: ?$ReadOnly<{
style?: ?('automatic' | 'soft' | 'hard'),
hidden?: ?boolean,
}>,
rightEdgeEffect?: ?$ReadOnly<{
style?: ?('automatic' | 'soft' | 'hard'),
hidden?: ?boolean,
}>,
zoomScale?: ?number,
// Overrides
onResponderGrant?: ?(e: GestureResponderEvent) => void | boolean,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,21 @@ static UIScrollViewIndicatorStyle RCTUIScrollViewIndicatorStyleFromProps(const S
}
}

#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 260000
API_AVAILABLE(ios(26.0))
static UIScrollEdgeEffectStyle *RCTUIScrollEdgeEffectStyleFromProps(ScrollViewEdgeEffectStyle style)
{
switch (style) {
case ScrollViewEdgeEffectStyle::Automatic:
return UIScrollEdgeEffectStyle.automaticStyle;
case ScrollViewEdgeEffectStyle::Soft:
return UIScrollEdgeEffectStyle.softStyle;
case ScrollViewEdgeEffectStyle::Hard:
return UIScrollEdgeEffectStyle.hardStyle;
}
}
#endif

// 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.
Expand Down Expand Up @@ -445,6 +460,43 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &
scrollView.keyboardDismissMode = RCTUIKeyboardDismissModeFromProps(newScrollViewProps);
}

#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 260000
if (@available(iOS 26.0, *)) {
if (oldScrollViewProps.topEdgeEffect != newScrollViewProps.topEdgeEffect) {
if (newScrollViewProps.topEdgeEffect.has_value()) {
if (newScrollViewProps.topEdgeEffect->style.has_value()) {
_scrollView.topEdgeEffect.style = RCTUIScrollEdgeEffectStyleFromProps(newScrollViewProps.topEdgeEffect->style.value());
}
_scrollView.topEdgeEffect.hidden = newScrollViewProps.topEdgeEffect->hidden;
}
}
if (oldScrollViewProps.bottomEdgeEffect != newScrollViewProps.bottomEdgeEffect) {
if (newScrollViewProps.bottomEdgeEffect.has_value()) {
if (newScrollViewProps.bottomEdgeEffect->style.has_value()) {
_scrollView.bottomEdgeEffect.style = RCTUIScrollEdgeEffectStyleFromProps(newScrollViewProps.bottomEdgeEffect->style.value());
}
_scrollView.bottomEdgeEffect.hidden = newScrollViewProps.bottomEdgeEffect->hidden;
}
}
if (oldScrollViewProps.leftEdgeEffect != newScrollViewProps.leftEdgeEffect) {
if (newScrollViewProps.leftEdgeEffect.has_value()) {
if (newScrollViewProps.leftEdgeEffect->style.has_value()) {
_scrollView.leftEdgeEffect.style = RCTUIScrollEdgeEffectStyleFromProps(newScrollViewProps.leftEdgeEffect->style.value());
}
_scrollView.leftEdgeEffect.hidden = newScrollViewProps.leftEdgeEffect->hidden;
}
}
if (oldScrollViewProps.rightEdgeEffect != newScrollViewProps.rightEdgeEffect) {
if (newScrollViewProps.rightEdgeEffect.has_value()) {
if (newScrollViewProps.rightEdgeEffect->style.has_value()) {
_scrollView.rightEdgeEffect.style = RCTUIScrollEdgeEffectStyleFromProps(newScrollViewProps.rightEdgeEffect->style.value());
}
_scrollView.rightEdgeEffect.hidden = newScrollViewProps.rightEdgeEffect->hidden;
}
}
}
#endif

[super updateProps:props oldProps:oldProps];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,42 @@ BaseScrollViewProps::BaseScrollViewProps(
rawProps,
"isInvertedVirtualizedList",
sourceProps.isInvertedVirtualizedList,
{})),
topEdgeEffect(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.topEdgeEffect
: convertRawProp(
context,
rawProps,
"topEdgeEffect",
sourceProps.topEdgeEffect,
{})),
bottomEdgeEffect(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.bottomEdgeEffect
: convertRawProp(
context,
rawProps,
"bottomEdgeEffect",
sourceProps.bottomEdgeEffect,
{})),
leftEdgeEffect(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.leftEdgeEffect
: convertRawProp(
context,
rawProps,
"leftEdgeEffect",
sourceProps.leftEdgeEffect,
{})),
rightEdgeEffect(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.rightEdgeEffect
: convertRawProp(
context,
rawProps,
"rightEdgeEffect",
sourceProps.rightEdgeEffect,
{})) {}

void BaseScrollViewProps::setProp(
Expand Down Expand Up @@ -425,6 +461,10 @@ void BaseScrollViewProps::setProp(
RAW_SET_PROP_SWITCH_CASE_BASIC(contentInsetAdjustmentBehavior);
RAW_SET_PROP_SWITCH_CASE_BASIC(scrollToOverflowEnabled);
RAW_SET_PROP_SWITCH_CASE_BASIC(isInvertedVirtualizedList);
RAW_SET_PROP_SWITCH_CASE_BASIC(topEdgeEffect);
RAW_SET_PROP_SWITCH_CASE_BASIC(bottomEdgeEffect);
RAW_SET_PROP_SWITCH_CASE_BASIC(leftEdgeEffect);
RAW_SET_PROP_SWITCH_CASE_BASIC(rightEdgeEffect);
}
}

Expand Down Expand Up @@ -559,7 +599,23 @@ SharedDebugStringConvertibleList BaseScrollViewProps::getDebugProps() const {
debugStringConvertibleItem(
"isInvertedVirtualizedList",
isInvertedVirtualizedList,
defaultScrollViewProps.isInvertedVirtualizedList)};
defaultScrollViewProps.isInvertedVirtualizedList),
debugStringConvertibleItem(
"topEdgeEffect",
topEdgeEffect,
defaultScrollViewProps.topEdgeEffect),
debugStringConvertibleItem(
"bottomEdgeEffect",
bottomEdgeEffect,
defaultScrollViewProps.bottomEdgeEffect),
debugStringConvertibleItem(
"leftEdgeEffect",
leftEdgeEffect,
defaultScrollViewProps.leftEdgeEffect),
debugStringConvertibleItem(
"rightEdgeEffect",
rightEdgeEffect,
defaultScrollViewProps.rightEdgeEffect)};
}
#endif

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ class BaseScrollViewProps : public ViewProps {
ContentInsetAdjustmentBehavior contentInsetAdjustmentBehavior{ContentInsetAdjustmentBehavior::Never};
bool scrollToOverflowEnabled{false};
bool isInvertedVirtualizedList{false};
std::optional<ScrollViewEdgeEffect> topEdgeEffect{};
std::optional<ScrollViewEdgeEffect> bottomEdgeEffect{};
std::optional<ScrollViewEdgeEffect> leftEdgeEffect{};
std::optional<ScrollViewEdgeEffect> rightEdgeEffect{};

#pragma mark - DebugStringConvertible

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,37 @@ fromRawValue(const PropsParserContext &context, const RawValue &value, ContentIn
abort();
}

inline void fromRawValue(const PropsParserContext &context, const RawValue &value, ScrollViewEdgeEffectStyle &result)
{
auto string = (std::string)value;
if (string == "automatic") {
result = ScrollViewEdgeEffectStyle::Automatic;
return;
}
if (string == "soft") {
result = ScrollViewEdgeEffectStyle::Soft;
return;
}
if (string == "hard") {
result = ScrollViewEdgeEffectStyle::Hard;
return;
}
abort();
}

inline void fromRawValue(const PropsParserContext &context, const RawValue &value, ScrollViewEdgeEffect &result)
{
auto map = (std::unordered_map<std::string, RawValue>)value;
auto iterator = map.find("style");
if (iterator != map.end()) {
fromRawValue(context, iterator->second, result.style.emplace());
}
iterator = map.find("hidden");
if (iterator != map.end()) {
fromRawValue(context, iterator->second, result.hidden);
}
}

inline void
fromRawValue(const PropsParserContext &context, const RawValue &value, ScrollViewMaintainVisibleContentPosition &result)
{
Expand Down Expand Up @@ -146,6 +177,38 @@ inline std::string toString(const ScrollViewKeyboardDismissMode &value)
}
}

inline std::string toString(const ScrollViewEdgeEffectStyle &value)
{
switch (value) {
case ScrollViewEdgeEffectStyle::Automatic:
return "automatic";
case ScrollViewEdgeEffectStyle::Soft:
return "soft";
case ScrollViewEdgeEffectStyle::Hard:
return "hard";
}
}

inline std::string toString(const ScrollViewEdgeEffect &value)
{
std::string result = "{style: ";
if (value.style.has_value()) {
result += toString(value.style.value());
} else {
result += "null";
}
result += ", hidden: " + std::string(value.hidden ? "true" : "false") + "}";
return result;
}

inline std::string toString(const std::optional<ScrollViewEdgeEffect> &value)
{
if (!value) {
return "null";
}
return toString(value.value());
}

inline std::string toString(const ContentInsetAdjustmentBehavior &value)
{
switch (value) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,25 @@ enum class ScrollViewKeyboardDismissMode { None, OnDrag, Interactive };

enum class ContentInsetAdjustmentBehavior { Never, Automatic, ScrollableAxes, Always };

enum class ScrollViewEdgeEffectStyle { Automatic, Soft, Hard };

class ScrollViewEdgeEffect final {
public:
std::optional<ScrollViewEdgeEffectStyle> style{};
bool hidden{false};

bool operator==(const ScrollViewEdgeEffect &rhs) const
{
return std::tie(this->style, this->hidden) ==
std::tie(rhs.style, rhs.hidden);
}

bool operator!=(const ScrollViewEdgeEffect &rhs) const
{
return !(*this == rhs);
}
};

class ScrollViewMaintainVisibleContentPosition final {
public:
int minIndexForVisible{0};
Expand Down
Loading
Loading