Skip to content
Merged
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
1 change: 1 addition & 0 deletions android/src/main/jni/RNKC.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <react/renderer/components/RNKC/RNKCKeyboardGestureAreaComponentDescriptor.h>
#include <react/renderer/components/RNKC/RNKCOverKeyboardViewComponentDescriptor.h>
#include <react/renderer/components/RNKC/RNKCKeyboardBackgroundViewComponentDescriptor.h>
#include <react/renderer/components/RNKC/RNKCClippingScrollViewDecoratorViewComponentDescriptor.h>

#include <memory>
#include <string>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// RNKCClippingScrollViewDecoratorViewComponentDescriptor.h
// Pods
//
// Created by Kiryl Ziusko on 03/03/2025.
//

#pragma once

#include "RNKCClippingScrollViewDecoratorViewShadowNode.h"

#include <react/debug/react_native_assert.h>
#include <react/renderer/components/RNKC/Props.h>
#include <react/renderer/core/ConcreteComponentDescriptor.h>

namespace facebook::react {
class ClippingScrollViewDecoratorViewComponentDescriptor final
: public ConcreteComponentDescriptor<ClippingScrollViewDecoratorViewShadowNode> {
public:
using ConcreteComponentDescriptor::ConcreteComponentDescriptor;
void adopt(ShadowNode &shadowNode) const override {
react_native_assert(dynamic_cast<ClippingScrollViewDecoratorViewShadowNode *>(&shadowNode));
ConcreteComponentDescriptor::adopt(shadowNode);
}
};

} // namespace facebook::react
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// RNKCClippingScrollViewDecoratorViewShadowNode.cpp
// Pods
//
// Created by Kiryl Ziusko on 03/03/2025.
//

#include "RNKCClippingScrollViewDecoratorViewShadowNode.h"

namespace facebook::react {

extern const char ClippingScrollViewDecoratorViewComponentName[] = "ClippingScrollViewDecoratorView";

} // namespace facebook::react
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//
// RNKCClippingScrollViewDecoratorViewShadowNode.h
// Pods
//
// Created by Kiryl Ziusko on 03/03/2025.
//

#pragma once

#include "RNKCClippingScrollViewDecoratorViewState.h"

#include <react/renderer/components/RNKC/EventEmitters.h>
#include <react/renderer/components/RNKC/Props.h>
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
#include <jsi/jsi.h>

namespace facebook::react {

JSI_EXPORT extern const char ClippingScrollViewDecoratorViewComponentName[];

/*
* `ShadowNode` for <ClippingScrollViewDecoratorView> component.
*/
using ClippingScrollViewDecoratorViewShadowNode = ConcreteViewShadowNode<
ClippingScrollViewDecoratorViewComponentName,
ClippingScrollViewDecoratorViewProps,
ClippingScrollViewDecoratorViewEventEmitter,
ClippingScrollViewDecoratorViewState>;

} // namespace facebook::react
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// RNKCClippingScrollViewDecoratorViewState.h
// Pods
//
// Created by Kiryl Ziusko on 03/03/2025.
//

#pragma once

#ifdef ANDROID
#include <folly/dynamic.h>
#endif

namespace facebook::react {

class ClippingScrollViewDecoratorViewState {
public:
ClippingScrollViewDecoratorViewState() = default;

#ifdef ANDROID
ClippingScrollViewDecoratorViewState(ClippingScrollViewDecoratorViewState const &previousState, folly::dynamic data) {}
folly::dynamic getDynamic() const {
return {};
}
#endif
};

} // namespace facebook::react
25 changes: 25 additions & 0 deletions ios/views/ClippingScrollViewDecoratorViewManager.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// ClippingScrollViewDecoratorViewManager.h
// Pods
//
// Created by Kiryl Ziusko on 03/03/2025.
//

#ifdef RCT_NEW_ARCH_ENABLED
#import <React/RCTViewComponentView.h>
#else
#import <React/RCTView.h>
#endif
#import <React/RCTViewManager.h>
#import <UIKit/UIKit.h>

@interface ClippingScrollViewDecoratorViewManager : RCTViewManager
@end

@interface ClippingScrollViewDecoratorView :
#ifdef RCT_NEW_ARCH_ENABLED
RCTViewComponentView
#else
UIView
#endif
@end
146 changes: 146 additions & 0 deletions ios/views/ClippingScrollViewDecoratorViewManager.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
//
// ClippingScrollViewDecoratorViewManager.mm
// Pods
//
// Created by Kiryl Ziusko on 03/03/2025.
//

#import "ClippingScrollViewDecoratorViewManager.h"

#ifdef RCT_NEW_ARCH_ENABLED
#import <react/renderer/components/RNKC/EventEmitters.h>
#import <react/renderer/components/RNKC/Props.h>
#import <react/renderer/components/RNKC/RCTComponentViewHelpers.h>
#import <react/renderer/components/RNKC/RNKCClippingScrollViewDecoratorViewComponentDescriptor.h>

#import "RCTFabricComponentsPlugins.h"
#endif

#import <UIKit/UIKit.h>
#import <objc/runtime.h>

#ifdef RCT_NEW_ARCH_ENABLED
using namespace facebook::react;
#endif

#pragma mark - Helpers

static UIScrollView *KCFindFirstScrollView(UIView *view)
{
for (UIView *subview in view.subviews) {
if ([subview isKindOfClass:[UIScrollView class]] &&
![subview isKindOfClass:[UITextView class]]) {
return (UIScrollView *)subview;
}
UIScrollView *found = KCFindFirstScrollView(subview);
if (found) {
return found;
}
}
return nil;
}

static void KCApplyNoopScrollRectToVisible(UIScrollView *scrollView)
{
if (!scrollView) {
return;
}

Class originalClass = object_getClass(scrollView);
NSString *originalClassName = NSStringFromClass(originalClass);

// Already patched β€” nothing to do
if ([originalClassName hasPrefix:@"KC_NoScrollRect_"]) {
return;
}

NSString *subclassName = [@"KC_NoScrollRect_" stringByAppendingString:originalClassName];
Class subclass = NSClassFromString(subclassName);

if (!subclass) {
subclass = objc_allocateClassPair(originalClass, subclassName.UTF8String, 0);
if (!subclass) {
return;
}

Method original =
class_getInstanceMethod(originalClass, @selector(scrollRectToVisible:animated:));
if (original) {
IMP noopImp = imp_implementationWithBlock(
^(__unused UIScrollView *self, __unused CGRect rect, __unused BOOL animated){
// no-op
});
class_addMethod(
subclass,
@selector(scrollRectToVisible:animated:),
noopImp,
method_getTypeEncoding(original));
}

objc_registerClassPair(subclass);
}

object_setClass(scrollView, subclass);
}

#pragma mark - Manager

@implementation ClippingScrollViewDecoratorViewManager

RCT_EXPORT_MODULE(ClippingScrollViewDecoratorViewManager)

+ (BOOL)requiresMainQueueSetup
{
return NO;
}

#ifndef RCT_NEW_ARCH_ENABLED
- (UIView *)view
{
return [[ClippingScrollViewDecoratorView alloc] init];
}
#endif

@end

#pragma mark - View

#ifdef RCT_NEW_ARCH_ENABLED
@interface ClippingScrollViewDecoratorView () <RCTClippingScrollViewDecoratorViewViewProtocol>
#else
@interface ClippingScrollViewDecoratorView ()
#endif
@end

@implementation ClippingScrollViewDecoratorView

#ifdef RCT_NEW_ARCH_ENABLED
+ (ComponentDescriptorProvider)componentDescriptorProvider
{
return concreteComponentDescriptorProvider<ClippingScrollViewDecoratorViewComponentDescriptor>();
}
#endif

// Needed because of this: https://github.com/facebook/react-native/pull/37274
+ (void)load
{
[super load];
}

- (void)didMoveToWindow
{
[super didMoveToWindow];
if (self.window) {
UIScrollView *scrollView = KCFindFirstScrollView(self);
KCApplyNoopScrollRectToVisible(scrollView);
}
}

#ifdef RCT_NEW_ARCH_ENABLE
Class<RCTComponentViewProtocol> ClippingScrollViewDecoratorViewCls(void)
{
return ClippingScrollViewDecoratorView.class;
}
#endif

@end
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,8 @@
"KeyboardGestureArea": "KeyboardGestureArea",
"OverKeyboardView": "OverKeyboardView",
"KeyboardBackgroundView": "KeyboardBackgroundView",
"KeyboardExtender": "KeyboardExtender"
"KeyboardExtender": "KeyboardExtender",
"ClippingScrollViewDecoratorView": "ClippingScrollViewDecoratorView"
}
}
},
Expand Down
1 change: 1 addition & 0 deletions react-native.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ module.exports = {
"KeyboardGestureAreaComponentDescriptor",
"OverKeyboardViewComponentDescriptor",
"KeyboardBackgroundViewComponentDescriptor",
"ClippingScrollViewDecoratorViewComponentDescriptor",
],
cmakeListsPath: "../android/src/main/jni/CMakeLists.txt",
},
Expand Down
5 changes: 1 addition & 4 deletions src/bindings.native.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { NativeEventEmitter, Platform } from "react-native";

import type {
ClippingScrollViewProps,
FocusedInputEventsModule,
KeyboardBackgroundViewProps,
KeyboardControllerNativeModule,
Expand Down Expand Up @@ -73,6 +72,4 @@ export const RCTKeyboardExtender: React.FC<KeyboardExtenderProps> =
? require("./specs/KeyboardExtenderNativeComponent").default
: ({ children }: KeyboardExtenderProps) => children;
export const ClippingScrollView: React.FC<KeyboardBackgroundViewProps> =
Platform.OS === "android"
? require("./specs/ClippingScrollViewDecoratorViewNativeComponent").default
: ({ children }: ClippingScrollViewProps) => children;
require("./specs/ClippingScrollViewDecoratorViewNativeComponent").default;
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,5 @@ export default codegenNativeComponent<NativeProps>(
"ClippingScrollViewDecoratorView",
{
interfaceOnly: true,
excludedPlatforms: ["iOS"],
},
) as HostComponent<NativeProps>;
Loading