Skip to content

Commit 0c409b6

Browse files
committed
feat: KeyboardToolbar.Exclude
1 parent 59b07bc commit 0c409b6

File tree

15 files changed

+205
-4
lines changed

15 files changed

+205
-4
lines changed

android/src/main/java/com/reactnativekeyboardcontroller/KeyboardControllerPackage.kt

+1
Original file line numberDiff line numberDiff line change
@@ -61,5 +61,6 @@ class KeyboardControllerPackage : TurboReactPackage() {
6161
KeyboardControllerViewManager(reactContext),
6262
KeyboardGestureAreaViewManager(reactContext),
6363
OverKeyboardViewManager(reactContext),
64+
KeyboardToolbarExcludeViewManager(reactContext),
6465
)
6566
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.reactnativekeyboardcontroller.managers
2+
3+
import com.facebook.react.bridge.ReactApplicationContext
4+
import com.facebook.react.uimanager.ThemedReactContext
5+
import com.reactnativekeyboardcontroller.views.KeyboardToolbarExcludeReactViewGroup
6+
import com.reactnativekeyboardcontroller.views.overlay.OverKeyboardHostView
7+
8+
@Suppress("detekt:UnusedPrivateProperty")
9+
class KeyboardToolbarExcludeViewManagerImpl(mReactContext: ReactApplicationContext) {
10+
fun createViewInstance(reactContext: ThemedReactContext): KeyboardToolbarExcludeReactViewGroup =
11+
KeyboardToolbarExcludeReactViewGroup(reactContext)
12+
13+
companion object {
14+
const val NAME = "KeyboardToolbarExcludeView"
15+
}
16+
}

android/src/main/java/com/reactnativekeyboardcontroller/traversal/ViewHierarchyNavigator.kt

+3-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import android.view.ViewGroup
55
import android.widget.EditText
66
import com.facebook.react.bridge.UiThreadUtil
77
import com.reactnativekeyboardcontroller.extensions.focus
8+
import com.reactnativekeyboardcontroller.views.KeyboardToolbarExcludeReactViewGroup
89

910
object ViewHierarchyNavigator {
1011
fun setFocusTo(
@@ -25,7 +26,7 @@ object ViewHierarchyNavigator {
2526
fun findEditTexts(view: View?) {
2627
if (isValidTextInput(view)) {
2728
editTexts.add(view as EditText)
28-
} else if (view is ViewGroup) {
29+
} else if (view is ViewGroup && view !is KeyboardToolbarExcludeReactViewGroup) {
2930
for (i in 0 until view.childCount) {
3031
findEditTexts(view.getChildAt(i))
3132
}
@@ -91,7 +92,7 @@ object ViewHierarchyNavigator {
9192

9293
if (isValidTextInput(child)) {
9394
result = child as EditText
94-
} else if (child is ViewGroup) {
95+
} else if (child is ViewGroup && child !is KeyboardToolbarExcludeReactViewGroup) {
9596
// If the child is a ViewGroup, check its children recursively
9697
result = findEditTextInHierarchy(child, direction)
9798
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.reactnativekeyboardcontroller.views
2+
3+
import android.annotation.SuppressLint
4+
import com.facebook.react.uimanager.ThemedReactContext
5+
import com.facebook.react.views.view.ReactViewGroup
6+
7+
@SuppressLint("ViewConstructor")
8+
class KeyboardToolbarExcludeReactViewGroup(reactContext: ThemedReactContext): ReactViewGroup(reactContext) {
9+
// semantic view used in KeyboardToolbar traverse algorithm
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.reactnativekeyboardcontroller
2+
3+
import com.facebook.react.bridge.ReactApplicationContext
4+
import com.facebook.react.uimanager.ThemedReactContext
5+
import com.facebook.react.views.view.ReactViewManager
6+
import com.reactnativekeyboardcontroller.managers.KeyboardToolbarExcludeViewManagerImpl
7+
import com.reactnativekeyboardcontroller.views.KeyboardToolbarExcludeReactViewGroup
8+
9+
class KeyboardToolbarExcludeViewManager(
10+
mReactContext: ReactApplicationContext,
11+
) : ReactViewManager() {
12+
private val manager = KeyboardToolbarExcludeViewManagerImpl(mReactContext)
13+
14+
override fun getName(): String = KeyboardToolbarExcludeViewManagerImpl.NAME
15+
16+
override fun createViewInstance(reactContext: ThemedReactContext): KeyboardToolbarExcludeReactViewGroup =
17+
manager.createViewInstance(reactContext)
18+
}

docs/docs/api/components/keyboard-toolbar/index.mdx

+6
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,12 @@ const theme: KeyboardToolbarProps["theme"] = {
281281
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.
282282
:::
283283

284+
## Components
285+
286+
### `KeyboardToolbar.Exclude`
287+
288+
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.
289+
284290
## Example
285291

286292
```tsx

example/src/screens/Examples/Toolbar/index.tsx

+10
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,16 @@ export default function ToolbarExample() {
150150
title="Flat"
151151
onFocus={onHideAutoFill}
152152
/>
153+
<KeyboardToolbar.Exclude>
154+
<TextInput
155+
contextMenuHidden
156+
keyboardType="numeric"
157+
placeholder="Excluded"
158+
testID="TextInput#14"
159+
title="Excluded"
160+
onFocus={onHideAutoFill}
161+
/>
162+
</KeyboardToolbar.Exclude>
153163
</KeyboardAwareScrollView>
154164
<KeyboardToolbar
155165
blur={blur}

ios/traversal/ViewHierarchyNavigator.swift

+3-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public class ViewHierarchyNavigator: NSObject {
4242

4343
if let textInput = isValidTextInput(view) {
4444
textInputs.append(textInput)
45-
} else {
45+
} else if (String(describing: type(of: view)) != "KeyboardToolbarExcludeView") {
4646
for subview in view.subviews {
4747
findTextInputs(in: subview)
4848
}
@@ -90,6 +90,8 @@ public class ViewHierarchyNavigator: NSObject {
9090
if let validTextInput = isValidTextInput(view) {
9191
return validTextInput
9292
}
93+
94+
guard String(describing: type(of: view)) != "KeyboardToolbarExcludeView" else { return nil }
9395

9496
// Determine the iteration order based on the direction
9597
let subviews = direction == "next" ? view.subviews : view.subviews.reversed()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//
2+
// KeyboardToolbarExcludeViewManager.h
3+
// KeyboardController
4+
//
5+
// Created by Kiryl Ziusko on 26/12/2024.
6+
//
7+
8+
#ifdef RCT_NEW_ARCH_ENABLED
9+
#import <React/RCTViewComponentView.h>
10+
#else
11+
#import <React/RCTBridge.h>
12+
#endif
13+
#import <React/RCTViewManager.h>
14+
#import <UIKit/UIKit.h>
15+
16+
@interface KeyboardToolbarExcludeViewManager : RCTViewManager
17+
@end
18+
19+
@interface KeyboardToolbarExcludeView :
20+
#ifdef RCT_NEW_ARCH_ENABLED
21+
RCTViewComponentView
22+
#else
23+
UIView
24+
25+
- (instancetype)initWithBridge:(RCTBridge *)bridge;
26+
27+
#endif
28+
@end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
//
2+
// KeyboardToolbarExcludeViewManager.mm
3+
// react-native-keyboard-controller
4+
//
5+
// Created by Kiryl Ziusko on 26/12/2024.
6+
//
7+
8+
#import "KeyboardToolbarExcludeViewManager.h"
9+
10+
#ifdef RCT_NEW_ARCH_ENABLED
11+
#import <react/renderer/components/reactnativekeyboardcontroller/ComponentDescriptors.h>
12+
#import <react/renderer/components/reactnativekeyboardcontroller/EventEmitters.h>
13+
#import <react/renderer/components/reactnativekeyboardcontroller/Props.h>
14+
#import <react/renderer/components/reactnativekeyboardcontroller/RCTComponentViewHelpers.h>
15+
16+
#import "RCTFabricComponentsPlugins.h"
17+
#endif
18+
19+
#import <UIKit/UIKit.h>
20+
21+
#ifdef RCT_NEW_ARCH_ENABLED
22+
using namespace facebook::react;
23+
#endif
24+
25+
// MARK: Manager
26+
@implementation KeyboardToolbarExcludeViewManager
27+
28+
RCT_EXPORT_MODULE(KeyboardToolbarExcludeViewManager)
29+
30+
+ (BOOL)requiresMainQueueSetup
31+
{
32+
return NO;
33+
}
34+
35+
#ifndef RCT_NEW_ARCH_ENABLED
36+
- (UIView *)view
37+
{
38+
return [[KeyboardToolbarExcludeView alloc] initWithBridge:self.bridge];
39+
}
40+
#endif
41+
42+
@end
43+
44+
// MARK: View
45+
#ifdef RCT_NEW_ARCH_ENABLED
46+
@interface KeyboardToolbarExcludeView () <RCTKeyboardToolbarExcludeViewViewProtocol>
47+
@end
48+
#endif
49+
50+
@implementation KeyboardToolbarExcludeView {}
51+
52+
#ifdef RCT_NEW_ARCH_ENABLED
53+
+ (ComponentDescriptorProvider)componentDescriptorProvider
54+
{
55+
return concreteComponentDescriptorProvider<KeyboardToolbarExcludeViewComponentDescriptor>();
56+
}
57+
#endif
58+
59+
// Needed because of this: https://github.com/facebook/react-native/pull/37274
60+
+ (void)load
61+
{
62+
[super load];
63+
}
64+
65+
// MARK: Constructor
66+
#ifdef RCT_NEW_ARCH_ENABLED
67+
- (instancetype)init
68+
{
69+
self = [super init];
70+
return self;
71+
}
72+
#else
73+
- (instancetype)initWithBridge:(RCTBridge *)bridge
74+
{
75+
self = [super init];
76+
return self;
77+
}
78+
#endif
79+
80+
81+
#ifdef RCT_NEW_ARCH_ENABLED
82+
Class<RCTComponentViewProtocol> KeyboardToolbarExcludeViewCls(void)
83+
{
84+
return KeyboardToolbarExcludeView.class;
85+
}
86+
#endif
87+
88+
@end

src/bindings.native.ts

+2
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,5 @@ export const KeyboardGestureArea: React.FC<KeyboardGestureAreaProps> =
6060
: ({ children }: KeyboardGestureAreaProps) => children;
6161
export const RCTOverKeyboardView: React.FC<OverKeyboardViewProps> =
6262
require("./specs/OverKeyboardViewNativeComponent").default;
63+
export const RCTKeyboardToolbarExcludeView: React.FC<OverKeyboardViewProps> =
64+
require("./specs/KeyboardToolbarExcludeViewNativeComponent").default;

src/bindings.ts

+3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type {
66
KeyboardControllerProps,
77
KeyboardEventsModule,
88
KeyboardGestureAreaProps,
9+
KeyboardToolbarExcludeViewProps,
910
OverKeyboardViewProps,
1011
WindowDimensionsEventsModule,
1112
} from "./types";
@@ -40,3 +41,5 @@ export const KeyboardGestureArea =
4041
View as unknown as React.FC<KeyboardGestureAreaProps>;
4142
export const RCTOverKeyboardView =
4243
View as unknown as React.FC<OverKeyboardViewProps>;
44+
export const RCTKeyboardToolbarExcludeView =
45+
View as unknown as React.FC<KeyboardToolbarExcludeViewProps>;

src/components/KeyboardToolbar/index.tsx

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import React, { useCallback, useEffect, useMemo, useState } from "react";
22
import { StyleSheet, Text, View } from "react-native";
33

4-
import { FocusedInputEvents } from "../../bindings";
4+
import {
5+
FocusedInputEvents,
6+
RCTKeyboardToolbarExcludeView,
7+
} from "../../bindings";
58
import { KeyboardController } from "../../module";
69
import useColorScheme from "../hooks/useColorScheme";
710
import KeyboardStickyView from "../KeyboardStickyView";
@@ -209,6 +212,8 @@ const KeyboardToolbar: React.FC<KeyboardToolbarProps> = ({
209212
);
210213
};
211214

215+
KeyboardToolbar.Exclude = RCTKeyboardToolbarExcludeView;
216+
212217
const styles = StyleSheet.create({
213218
flex: {
214219
flex: 1,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import codegenNativeComponent from "react-native/Libraries/Utilities/codegenNativeComponent";
2+
3+
import type { HostComponent } from "react-native";
4+
import type { ViewProps } from "react-native/Libraries/Components/View/ViewPropTypes";
5+
6+
export interface NativeProps extends ViewProps {}
7+
8+
export default codegenNativeComponent<NativeProps>(
9+
"KeyboardToolbarExcludeView",
10+
) as HostComponent<NativeProps>;

src/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ export type KeyboardGestureAreaProps = {
113113
export type OverKeyboardViewProps = PropsWithChildren<{
114114
visible: boolean;
115115
}>;
116+
export type KeyboardToolbarExcludeViewProps = ViewProps;
116117

117118
export type Direction = "next" | "prev" | "current";
118119
export type DismissOptions = {

0 commit comments

Comments
 (0)