diff --git a/android/src/base/java/com/reactnativekeyboardcontroller/KeyboardControllerPackage.kt b/android/src/base/java/com/reactnativekeyboardcontroller/KeyboardControllerPackage.kt index 59f43bfc9c..8a3ce459a2 100644 --- a/android/src/base/java/com/reactnativekeyboardcontroller/KeyboardControllerPackage.kt +++ b/android/src/base/java/com/reactnativekeyboardcontroller/KeyboardControllerPackage.kt @@ -57,5 +57,6 @@ class KeyboardControllerPackage : BaseReactPackage() { KeyboardControllerViewManager(reactContext), KeyboardGestureAreaViewManager(reactContext), OverKeyboardViewManager(reactContext), + KeyboardToolbarExcludeViewManager(reactContext), ) } diff --git a/android/src/main/java/com/reactnativekeyboardcontroller/managers/KeyboardToolbarExcludeViewManagerImpl.kt b/android/src/main/java/com/reactnativekeyboardcontroller/managers/KeyboardToolbarExcludeViewManagerImpl.kt new file mode 100644 index 0000000000..5a3078db0e --- /dev/null +++ b/android/src/main/java/com/reactnativekeyboardcontroller/managers/KeyboardToolbarExcludeViewManagerImpl.kt @@ -0,0 +1,17 @@ +package com.reactnativekeyboardcontroller.managers + +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.uimanager.ThemedReactContext +import com.reactnativekeyboardcontroller.views.KeyboardToolbarExcludeReactViewGroup + +@Suppress("detekt:UnusedPrivateProperty") +class KeyboardToolbarExcludeViewManagerImpl( + mReactContext: ReactApplicationContext, +) { + fun createViewInstance(reactContext: ThemedReactContext): KeyboardToolbarExcludeReactViewGroup = + KeyboardToolbarExcludeReactViewGroup(reactContext) + + companion object { + const val NAME = "KeyboardToolbarExcludeView" + } +} diff --git a/android/src/main/java/com/reactnativekeyboardcontroller/traversal/ViewHierarchyNavigator.kt b/android/src/main/java/com/reactnativekeyboardcontroller/traversal/ViewHierarchyNavigator.kt index 8a0d6ec4ee..975aec8e84 100644 --- a/android/src/main/java/com/reactnativekeyboardcontroller/traversal/ViewHierarchyNavigator.kt +++ b/android/src/main/java/com/reactnativekeyboardcontroller/traversal/ViewHierarchyNavigator.kt @@ -5,6 +5,7 @@ import android.view.ViewGroup import android.widget.EditText import com.facebook.react.bridge.UiThreadUtil import com.reactnativekeyboardcontroller.extensions.focus +import com.reactnativekeyboardcontroller.views.KeyboardToolbarExcludeReactViewGroup object ViewHierarchyNavigator { fun setFocusTo( @@ -25,7 +26,7 @@ object ViewHierarchyNavigator { fun findEditTexts(view: View?) { if (isValidTextInput(view)) { editTexts.add(view as EditText) - } else if (view is ViewGroup) { + } else if (view is ViewGroup && view !is KeyboardToolbarExcludeReactViewGroup) { for (i in 0 until view.childCount) { findEditTexts(view.getChildAt(i)) } @@ -91,7 +92,7 @@ object ViewHierarchyNavigator { if (isValidTextInput(child)) { result = child as EditText - } else if (child is ViewGroup) { + } else if (child is ViewGroup && child !is KeyboardToolbarExcludeReactViewGroup) { // If the child is a ViewGroup, check its children recursively result = findEditTextInHierarchy(child, direction) } diff --git a/android/src/main/java/com/reactnativekeyboardcontroller/views/KeyboardToolbarExcludeReactViewGroup.kt b/android/src/main/java/com/reactnativekeyboardcontroller/views/KeyboardToolbarExcludeReactViewGroup.kt new file mode 100644 index 0000000000..2ef1253f34 --- /dev/null +++ b/android/src/main/java/com/reactnativekeyboardcontroller/views/KeyboardToolbarExcludeReactViewGroup.kt @@ -0,0 +1,12 @@ +package com.reactnativekeyboardcontroller.views + +import android.annotation.SuppressLint +import com.facebook.react.uimanager.ThemedReactContext +import com.facebook.react.views.view.ReactViewGroup + +@SuppressLint("ViewConstructor") +class KeyboardToolbarExcludeReactViewGroup( + reactContext: ThemedReactContext, +) : ReactViewGroup(reactContext) { + // semantic view used in KeyboardToolbar traverse algorithm +} diff --git a/android/src/paper/java/com/reactnativekeyboardcontroller/KeyboardToolbarExcludeViewManager.kt b/android/src/paper/java/com/reactnativekeyboardcontroller/KeyboardToolbarExcludeViewManager.kt new file mode 100644 index 0000000000..c27b93e93a --- /dev/null +++ b/android/src/paper/java/com/reactnativekeyboardcontroller/KeyboardToolbarExcludeViewManager.kt @@ -0,0 +1,18 @@ +package com.reactnativekeyboardcontroller + +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.uimanager.ThemedReactContext +import com.facebook.react.views.view.ReactViewManager +import com.reactnativekeyboardcontroller.managers.KeyboardToolbarExcludeViewManagerImpl +import com.reactnativekeyboardcontroller.views.KeyboardToolbarExcludeReactViewGroup + +class KeyboardToolbarExcludeViewManager( + mReactContext: ReactApplicationContext, +) : ReactViewManager() { + private val manager = KeyboardToolbarExcludeViewManagerImpl(mReactContext) + + override fun getName(): String = KeyboardToolbarExcludeViewManagerImpl.NAME + + override fun createViewInstance(reactContext: ThemedReactContext): KeyboardToolbarExcludeReactViewGroup = + manager.createViewInstance(reactContext) +} diff --git a/android/src/turbo/java/com/reactnativekeyboardcontroller/KeyboardControllerPackage.kt b/android/src/turbo/java/com/reactnativekeyboardcontroller/KeyboardControllerPackage.kt index 4d99de4b85..060aeeac78 100644 --- a/android/src/turbo/java/com/reactnativekeyboardcontroller/KeyboardControllerPackage.kt +++ b/android/src/turbo/java/com/reactnativekeyboardcontroller/KeyboardControllerPackage.kt @@ -59,5 +59,6 @@ class KeyboardControllerPackage : TurboReactPackage() { KeyboardControllerViewManager(reactContext), KeyboardGestureAreaViewManager(reactContext), OverKeyboardViewManager(reactContext), + KeyboardToolbarExcludeViewManager(reactContext), ) } diff --git a/docs/docs/api/components/keyboard-toolbar/index.mdx b/docs/docs/api/components/keyboard-toolbar/index.mdx index d0fbaee09a..8e3c124df2 100644 --- a/docs/docs/api/components/keyboard-toolbar/index.mdx +++ b/docs/docs/api/components/keyboard-toolbar/index.mdx @@ -295,6 +295,26 @@ const theme: KeyboardToolbarProps["theme"] = { Don't forget that you need to specify colors for **both** `dark` and `light` theme. The theme will be selected automatically based on the device preferences. ::: +## Components + +### `KeyboardToolbar.Exclude` + +This component is used to exclude some views from the traversal. It is useful when you want to skip specific view from being focused by toolbar arrow buttons. + +```tsx + + + + + +``` + ## Example ```tsx diff --git a/example/src/screens/Examples/Toolbar/index.tsx b/example/src/screens/Examples/Toolbar/index.tsx index a8d9d2b324..101f2a6205 100644 --- a/example/src/screens/Examples/Toolbar/index.tsx +++ b/example/src/screens/Examples/Toolbar/index.tsx @@ -154,6 +154,16 @@ function Form() { title="Flat" onFocus={onHideAutoFill} /> + + + +#else +#import +#endif +#import +#import + +@interface KeyboardToolbarExcludeViewManager : RCTViewManager +@end + +@interface KeyboardToolbarExcludeView : +#ifdef RCT_NEW_ARCH_ENABLED + RCTViewComponentView +#else + UIView + +- (instancetype)initWithBridge:(RCTBridge *)bridge; + +#endif +@end diff --git a/ios/views/KeyboardToolbarExcludeViewManager.mm b/ios/views/KeyboardToolbarExcludeViewManager.mm new file mode 100644 index 0000000000..46fda7bdd4 --- /dev/null +++ b/ios/views/KeyboardToolbarExcludeViewManager.mm @@ -0,0 +1,88 @@ +// +// KeyboardToolbarExcludeViewManager.mm +// react-native-keyboard-controller +// +// Created by Kiryl Ziusko on 26/12/2024. +// + +#import "KeyboardToolbarExcludeViewManager.h" + +#ifdef RCT_NEW_ARCH_ENABLED +#import +#import +#import +#import + +#import "RCTFabricComponentsPlugins.h" +#endif + +#import + +#ifdef RCT_NEW_ARCH_ENABLED +using namespace facebook::react; +#endif + +// MARK: Manager +@implementation KeyboardToolbarExcludeViewManager + +RCT_EXPORT_MODULE(KeyboardToolbarExcludeViewManager) + ++ (BOOL)requiresMainQueueSetup +{ + return NO; +} + +#ifndef RCT_NEW_ARCH_ENABLED +- (UIView *)view +{ + return [[KeyboardToolbarExcludeView alloc] initWithBridge:self.bridge]; +} +#endif + +@end + +// MARK: View +#ifdef RCT_NEW_ARCH_ENABLED +@interface KeyboardToolbarExcludeView () +@end +#endif + +@implementation KeyboardToolbarExcludeView { +} + +#ifdef RCT_NEW_ARCH_ENABLED ++ (ComponentDescriptorProvider)componentDescriptorProvider +{ + return concreteComponentDescriptorProvider(); +} +#endif + +// Needed because of this: https://github.com/facebook/react-native/pull/37274 ++ (void)load +{ + [super load]; +} + +// MARK: Constructor +#ifdef RCT_NEW_ARCH_ENABLED +- (instancetype)init +{ + self = [super init]; + return self; +} +#else +- (instancetype)initWithBridge:(RCTBridge *)bridge +{ + self = [super init]; + return self; +} +#endif + +#ifdef RCT_NEW_ARCH_ENABLED +Class KeyboardToolbarExcludeViewCls(void) +{ + return KeyboardToolbarExcludeView.class; +} +#endif + +@end diff --git a/src/bindings.native.ts b/src/bindings.native.ts index d1621d1838..b5267dcb5e 100644 --- a/src/bindings.native.ts +++ b/src/bindings.native.ts @@ -60,3 +60,5 @@ export const KeyboardGestureArea: React.FC = : ({ children }: KeyboardGestureAreaProps) => children; export const RCTOverKeyboardView: React.FC = require("./specs/OverKeyboardViewNativeComponent").default; +export const RCTKeyboardToolbarExcludeView: React.FC = + require("./specs/KeyboardToolbarExcludeViewNativeComponent").default; diff --git a/src/bindings.ts b/src/bindings.ts index a82850206a..45fd24416f 100644 --- a/src/bindings.ts +++ b/src/bindings.ts @@ -6,6 +6,7 @@ import type { KeyboardControllerProps, KeyboardEventsModule, KeyboardGestureAreaProps, + KeyboardToolbarExcludeViewProps, OverKeyboardViewProps, WindowDimensionsEventsModule, } from "./types"; @@ -40,3 +41,5 @@ export const KeyboardGestureArea = View as unknown as React.FC; export const RCTOverKeyboardView = View as unknown as React.FC; +export const RCTKeyboardToolbarExcludeView = + View as unknown as React.FC; diff --git a/src/components/KeyboardToolbar/index.tsx b/src/components/KeyboardToolbar/index.tsx index dd4e969112..f57253bc45 100644 --- a/src/components/KeyboardToolbar/index.tsx +++ b/src/components/KeyboardToolbar/index.tsx @@ -1,7 +1,10 @@ import React, { useCallback, useEffect, useMemo, useState } from "react"; import { StyleSheet, Text, View } from "react-native"; -import { FocusedInputEvents } from "../../bindings"; +import { + FocusedInputEvents, + RCTKeyboardToolbarExcludeView, +} from "../../bindings"; import { KeyboardController } from "../../module"; import useColorScheme from "../hooks/useColorScheme"; import KeyboardStickyView from "../KeyboardStickyView"; @@ -71,11 +74,15 @@ const TEST_ID_KEYBOARD_TOOLBAR_DONE = `${TEST_ID_KEYBOARD_TOOLBAR}.done`; const KEYBOARD_TOOLBAR_HEIGHT = 42; const DEFAULT_OPACITY: HEX = "FF"; +type KeyboardToolbarComponent = { + Exclude: typeof RCTKeyboardToolbarExcludeView; +} & React.FC; + /** * `KeyboardToolbar` is a component that is shown above the keyboard with `Prev`/`Next` and * `Done` buttons. */ -const KeyboardToolbar: React.FC = ({ +const KeyboardToolbar: KeyboardToolbarComponent = ({ content, theme = colors, doneText = "Done", @@ -222,6 +229,8 @@ const KeyboardToolbar: React.FC = ({ ); }; +KeyboardToolbar.Exclude = RCTKeyboardToolbarExcludeView; + const styles = StyleSheet.create({ flex: { flex: 1, diff --git a/src/specs/KeyboardToolbarExcludeViewNativeComponent.ts b/src/specs/KeyboardToolbarExcludeViewNativeComponent.ts new file mode 100644 index 0000000000..bf6c612caf --- /dev/null +++ b/src/specs/KeyboardToolbarExcludeViewNativeComponent.ts @@ -0,0 +1,10 @@ +import codegenNativeComponent from "react-native/Libraries/Utilities/codegenNativeComponent"; + +import type { HostComponent } from "react-native"; +import type { ViewProps } from "react-native/Libraries/Components/View/ViewPropTypes"; + +export interface NativeProps extends ViewProps {} + +export default codegenNativeComponent( + "KeyboardToolbarExcludeView", +) as HostComponent; diff --git a/src/types.ts b/src/types.ts index 0ec0e48be5..130a423112 100644 --- a/src/types.ts +++ b/src/types.ts @@ -118,6 +118,7 @@ export type KeyboardGestureAreaProps = { export type OverKeyboardViewProps = PropsWithChildren<{ visible: boolean; }>; +export type KeyboardToolbarExcludeViewProps = ViewProps; export type Direction = "next" | "prev" | "current"; export type DismissOptions = {