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
2 changes: 1 addition & 1 deletion FabricExample/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3184,7 +3184,7 @@ SPEC CHECKSUMS:
RNCMaskedView: 63268de1986a098b5f4d1fb5b1bc1e97fade0aee
RNGestureHandler: 4f7cc97a71d4fe0fcba38c94acdd969f5f17c91c
RNReactNativeHapticFeedback: 63aa39dbd6ef56e9de688210c5761d4007927a7e
RNReanimated: f2cdef8c5ec70e440498b949f9af3ea39f70fcec
RNReanimated: aabfea4a99566babeedbd3e33eb234b9756932bd
RNScreens: 74985ca8e102294a60cec7513fa84c936fa0b20b
RNWorklets: ab7740c2a152f77ff76a40d52be860d8f128fab1
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.reactnativekeyboardcontroller

import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.reactnativekeyboardcontroller.modules.KeyboardControllerModuleImpl

Expand Down Expand Up @@ -33,6 +34,13 @@ class KeyboardControllerModule(
module.setFocusTo(direction)
}

override fun viewPositionInWindow(
viewTag: Double,
promise: Promise,
) {
module.viewPositionInWindow(viewTag, promise)
}

override fun addListener(eventName: String?) {
// Required for RN built-in Event Emitter Calls.
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,18 @@ package com.reactnativekeyboardcontroller.extensions
import android.view.View
import android.view.ViewGroup
import com.facebook.react.bridge.ReactContext
import com.facebook.react.uimanager.UIManagerHelper
import com.facebook.react.uimanager.common.UIManagerType
import com.facebook.react.uimanager.events.EventDispatcher
import com.reactnativekeyboardcontroller.BuildConfig

private val archType = if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) UIManagerType.FABRIC else UIManagerType.DEFAULT

val ReactContext.uiManager
get() = UIManagerHelper.getUIManager(this, archType)

val ReactContext.eventDispatcher: EventDispatcher?
get() = UIManagerHelper.getEventDispatcher(this, archType)

val ReactContext.rootView: View?
get() =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@ import android.view.ViewGroup
import android.view.WindowManager
import androidx.core.view.ViewCompat
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.UIManagerHelper
import com.facebook.react.uimanager.common.UIManagerType
import com.facebook.react.uimanager.events.Event
import com.facebook.react.uimanager.events.EventDispatcherListener
import com.facebook.react.views.modal.ReactModalHostView
import com.facebook.react.views.view.ReactViewGroup
import com.reactnativekeyboardcontroller.BuildConfig
import com.reactnativekeyboardcontroller.constants.Keyboard
import com.reactnativekeyboardcontroller.extensions.eventDispatcher
import com.reactnativekeyboardcontroller.extensions.removeSelf
import com.reactnativekeyboardcontroller.extensions.uiManager
import com.reactnativekeyboardcontroller.listeners.KeyboardAnimationCallback
import com.reactnativekeyboardcontroller.listeners.KeyboardAnimationCallbackConfig
import com.reactnativekeyboardcontroller.log.Logger
Expand All @@ -26,9 +25,8 @@ class ModalAttachedWatcher(
private val config: KeyboardAnimationCallbackConfig,
private var callback: () -> KeyboardAnimationCallback?,
) : EventDispatcherListener {
private val archType = if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) UIManagerType.FABRIC else UIManagerType.DEFAULT
private val uiManager = UIManagerHelper.getUIManager(reactContext.reactApplicationContext, archType)
private val eventDispatcher = UIManagerHelper.getEventDispatcher(reactContext.reactApplicationContext, archType)
private val uiManager = reactContext.uiManager
private val eventDispatcher = reactContext.eventDispatcher

override fun onEventDispatch(event: Event<*>) {
if (event.eventName != MODAL_SHOW_EVENT) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,21 @@ import android.os.Build
import android.view.View
import android.view.WindowManager
import android.view.inputmethod.InputMethodManager
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.UiThreadUtil
import com.reactnativekeyboardcontroller.extensions.dp
import com.reactnativekeyboardcontroller.extensions.screenLocation
import com.reactnativekeyboardcontroller.extensions.uiManager
import com.reactnativekeyboardcontroller.interactive.KeyboardAnimationController
import com.reactnativekeyboardcontroller.traversal.FocusedInputHolder
import com.reactnativekeyboardcontroller.traversal.ViewHierarchyNavigator

class KeyboardControllerModuleImpl(
private val mReactContext: ReactApplicationContext,
) {
private val uiManager = mReactContext.uiManager
private val controller = KeyboardAnimationController()
private val mDefaultMode: Int = getCurrentMode()

Expand Down Expand Up @@ -77,6 +83,26 @@ class KeyboardControllerModuleImpl(
}
}

fun viewPositionInWindow(
viewTag: Double,
promise: Promise,
) {
UiThreadUtil.runOnUiThread {
val view = uiManager?.resolveView(viewTag.toInt())
if (view == null) {
promise.reject("E_VIEW_NOT_FOUND", "Could not find view for tag")
return@runOnUiThread
}
val location = view.screenLocation
val map = Arguments.createMap()
map.putDouble("x", location[0].toFloat().dp)
map.putDouble("y", location[1].toFloat().dp)
map.putDouble("width", view.width.toFloat().dp)
map.putDouble("height", view.height.toFloat().dp)
promise.resolve(map)
}
}

private fun setSoftInputMode(mode: Int) {
UiThreadUtil.runOnUiThread {
if (getCurrentMode() != mode) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.reactnativekeyboardcontroller

import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
Expand Down Expand Up @@ -40,6 +41,14 @@ class KeyboardControllerModule(
module.setFocusTo(direction)
}

@ReactMethod
fun viewPositionInWindow(
viewTag: Double,
promise: Promise,
) {
module.viewPositionInWindow(viewTag, promise)
}

@Suppress("detekt:UnusedParameter")
@ReactMethod
fun addListener(eventName: String?) {
Expand Down
36 changes: 36 additions & 0 deletions ios/KeyboardControllerModule.mm
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
#endif

#import <React/RCTEventDispatcherProtocol.h>
#ifndef RCT_NEW_ARCH_ENABLED
#import <React/RCTUIManager.h>
#endif

#ifdef RCT_NEW_ARCH_ENABLED
@interface KeyboardController () <NativeKeyboardControllerSpec>
Expand Down Expand Up @@ -95,6 +98,39 @@ - (void)setFocusTo:(NSString *)direction
[ViewHierarchyNavigator setFocusToDirection:direction];
}

#ifdef RCT_NEW_ARCH_ENABLED
- (void)viewPositionInWindow:(double)viewTag
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject
#else
RCT_EXPORT_METHOD(viewPositionInWindow
: (nonnull NSNumber *)viewTag resolve
: (RCTPromiseResolveBlock)resolve reject
: (RCTPromiseRejectBlock)reject)
#endif
{
dispatch_async(dispatch_get_main_queue(), ^{
UIView *view = nil;
#ifdef RCT_NEW_ARCH_ENABLED
NSInteger tag = (NSInteger)viewTag;
view = [UIApplication.sharedApplication.activeWindow viewWithTag:tag];
#else
view = [self.bridge.uiManager viewForReactTag:viewTag];
#endif
if (!view || !view.superview) {
reject(@"E_VIEW_NOT_FOUND", @"Could not find view for tag", nil);
return;
}
CGRect frame = [view.superview convertRect:view.frame toView:nil];
resolve(@{
@"x" : @(frame.origin.x),
@"y" : @(frame.origin.y),
@"width" : @(frame.size.width),
@"height" : @(frame.size.height),
});
});
}

+ (KeyboardController *)shared
{
return shared;
Expand Down
2 changes: 1 addition & 1 deletion ios/extensions/UIApplication.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import Foundation
import UIKit

public extension UIApplication {
var activeWindow: UIWindow? {
@objc var activeWindow: UIWindow? {
if #available(iOS 13.0, *) {
for scene in connectedScenes {
if scene.activationState == .foregroundActive,
Expand Down
3 changes: 3 additions & 0 deletions jest/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ const mock = {
setFocusTo: jest.fn(),
isVisible: jest.fn().mockReturnValue(false),
state: jest.fn().mockReturnValue(lastKeyboardEvent),
viewPositionInWindow: jest
.fn()
.mockReturnValue(Promise.resolve({ x: 0, y: 0, width: 0, height: 0 })),
},
AndroidSoftInputModes: {
SOFT_INPUT_ADJUST_NOTHING: 48,
Expand Down
2 changes: 2 additions & 0 deletions src/bindings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export const KeyboardControllerNative: KeyboardControllerNativeModule = {
preload: NOOP,
dismiss: NOOP,
setFocusTo: NOOP,
viewPositionInWindow: () =>
Promise.resolve({ x: 0, y: 0, width: 0, height: 0 }),
addListener: NOOP,
removeListeners: NOOP,
};
Expand Down
31 changes: 24 additions & 7 deletions src/components/KeyboardAvoidingView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import Reanimated, {
useSharedValue,
} from "react-native-reanimated";

import { KeyboardControllerNative } from "../../bindings";
import { useWindowDimensions } from "../../hooks";
import { findNodeHandle } from "../../utils/findNodeHandle";
import useCombinedRef from "../hooks/useCombinedRef";

import { useKeyboardAnimation, useTranslateAnimation } from "./hooks";
Expand Down Expand Up @@ -131,18 +133,33 @@ const KeyboardAvoidingView = forwardRef<
);
const onLayout = useCallback<NonNullable<ViewProps["onLayout"]>>(
(e) => {
onLayoutProps?.(e);

const layout = e.nativeEvent.layout;

if (automaticOffset) {
// ref is always set here — onLayout only fires after mount
internalRef.current?.measureInWindow((x, y) => {
runOnUI(onLayoutWorklet)({ ...layout, x, y });
});
} else {
runOnUI(onLayoutWorklet)(layout);
const tag = findNodeHandle(internalRef.current);

if (tag !== null) {
// Use native `viewPositionInWindow` to get true screen-absolute coordinates.
// This fixes current RN bugs:
// - https://github.com/facebook/react-native/pull/56062
// - https://github.com/facebook/react-native/pull/56056
return KeyboardControllerNative.viewPositionInWindow(tag)
.then((position) => {
runOnUI(onLayoutWorklet)({
...layout,
x: position.x,
y: position.y,
});
})
.catch(() => {
runOnUI(onLayoutWorklet)(layout);
});
}
}

onLayoutProps?.(e);
return runOnUI(onLayoutWorklet)(layout);
},
[onLayoutProps, automaticOffset],
);
Expand Down
1 change: 1 addition & 0 deletions src/specs/NativeKeyboardController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export interface Spec extends TurboModule {
preload(): void;
dismiss(keepFocus: boolean, animated: boolean): void;
setFocusTo(direction: string): void;
viewPositionInWindow(viewTag: number): Promise<object>;

// event emitter
addListener: (eventName: string) => void;
Expand Down
9 changes: 9 additions & 0 deletions src/types/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,12 @@ export type KeyboardControllerModule = {
*/
state: () => KeyboardEventData;
};
export type ViewPositionInWindowResult = {
x: number;
y: number;
width: number;
height: number;
};
export type KeyboardControllerNativeModule = {
// android only
setDefaultMode: () => void;
Expand All @@ -118,6 +124,9 @@ export type KeyboardControllerNativeModule = {
// all platforms
dismiss: (keepFocus: boolean, animated: boolean) => void;
setFocusTo: (direction: Direction) => void;
viewPositionInWindow: (
viewTag: number,
) => Promise<ViewPositionInWindowResult>;
// native event module stuff
addListener: (eventName: string) => void;
removeListeners: (count: number) => void;
Expand Down
Loading