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
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,38 @@ exports[`components rendering should render \`OverKeyboardView\` 1`] = `
/>
</OverKeyboardView>
`;

exports[`components rendering should render compound \`KeyboardToolbar\` 1`] = `
[
<View>
<KeyboardToolbar.Background>
<View
style={
{
"backgroundColor": "black",
"height": 20,
"width": 20,
}
}
/>
</KeyboardToolbar.Background>
<KeyboardToolbar.Content>
<View
style={
{
"backgroundColor": "black",
"height": 20,
"width": 20,
}
}
/>
</KeyboardToolbar.Content>
<KeyboardToolbar.Prev />
<KeyboardToolbar.Next />
<KeyboardToolbar.Done />
</View>,
<KeyboardToolbar.Group>
<TextInput />
</KeyboardToolbar.Group>,
]
`;
27 changes: 26 additions & 1 deletion FabricExample/__tests__/components-rendering.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { render } from "@testing-library/react-native";
import React from "react";
import { View } from "react-native";
import { TextInput, View } from "react-native";
import {
KeyboardAvoidingView,
KeyboardAwareScrollView,
Expand Down Expand Up @@ -68,6 +68,27 @@ function KeyboardToolbarTest() {
return <KeyboardToolbar content={content} />;
}

function KeyboardToolbarCompoundTest() {
return (
<>
<KeyboardToolbar>
<KeyboardToolbar.Background>
<EmptyView />
</KeyboardToolbar.Background>
<KeyboardToolbar.Content>
<EmptyView />
</KeyboardToolbar.Content>
<KeyboardToolbar.Prev />
<KeyboardToolbar.Next />
<KeyboardToolbar.Done />
</KeyboardToolbar>
<KeyboardToolbar.Group>
<TextInput />
</KeyboardToolbar.Group>
</>
);
}

function OverKeyboardViewTest() {
return (
<OverKeyboardView visible={true}>
Expand Down Expand Up @@ -109,6 +130,10 @@ describe("components rendering", () => {
expect(render(<KeyboardToolbarTest />)).toMatchSnapshot();
});

it("should render compound `KeyboardToolbar`", () => {
expect(render(<KeyboardToolbarCompoundTest />)).toMatchSnapshot();
});

it("should render `OverKeyboardView`", () => {
expect(render(<OverKeyboardViewTest />)).toMatchSnapshot();
});
Expand Down
22 changes: 22 additions & 0 deletions FabricExample/src/screens/Examples/Toolbar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import BottomSheet, { BottomSheetView } from "@gorhom/bottom-sheet";
import React, { useCallback, useEffect, useState } from "react";
import { Modal, Platform, StyleSheet, Text, View } from "react-native";
import {
Expand Down Expand Up @@ -214,6 +215,24 @@ export default function ToolbarExample({ navigation }: Props) {
<Form />
</View>
</Modal>
<BottomSheet index={-1}>
<BottomSheetView style={styles.bottomSheetContent}>
<KeyboardToolbar.Group>
<TextInput
keyboardType="default"
placeholder="Group input 1"
testID="TextInput#14"
title="Group Input 1"
/>
<TextInput
keyboardType="default"
placeholder="Group input 2"
testID="TextInput#15"
title="Group Input 2"
/>
</KeyboardToolbar.Group>
</BottomSheetView>
</BottomSheet>
</>
);
}
Expand Down Expand Up @@ -245,4 +264,7 @@ const styles = StyleSheet.create({
modal: {
marginTop: 32,
},
bottomSheetContent: {
flex: 1,
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,6 @@ class KeyboardControllerPackage : BaseReactPackage() {
OverKeyboardViewManager(),
KeyboardBackgroundViewManager(),
ClippingScrollViewDecoratorViewManager(),
KeyboardToolbarGroupViewManager(),
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.reactnativekeyboardcontroller

import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.ViewGroupManager
import com.facebook.react.uimanager.ViewManagerDelegate
import com.facebook.react.viewmanagers.KeyboardToolbarGroupViewManagerDelegate
import com.facebook.react.viewmanagers.KeyboardToolbarGroupViewManagerInterface
import com.reactnativekeyboardcontroller.managers.KeyboardToolbarGroupViewManagerImpl
import com.reactnativekeyboardcontroller.views.KeyboardToolbarGroupReactViewGroup

class KeyboardToolbarGroupViewManager :
ViewGroupManager<KeyboardToolbarGroupReactViewGroup>(),
KeyboardToolbarGroupViewManagerInterface<KeyboardToolbarGroupReactViewGroup> {
private val manager = KeyboardToolbarGroupViewManagerImpl()
private val mDelegate = KeyboardToolbarGroupViewManagerDelegate(this)

override fun getDelegate(): ViewManagerDelegate<KeyboardToolbarGroupReactViewGroup> = mDelegate

override fun getName(): String = KeyboardToolbarGroupViewManagerImpl.NAME

override fun createViewInstance(context: ThemedReactContext): KeyboardToolbarGroupReactViewGroup =
manager.createViewInstance(context)
}
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,8 @@ class FocusedInputObserver(
selectionSubscription = newFocus.addOnSelectionChangedListener(selectionListener)
FocusedInputHolder.set(newFocus)

val allInputFields = ViewHierarchyNavigator.getAllInputFields(context?.rootView)
val groupAncestor = ViewHierarchyNavigator.findGroupAncestor(newFocus)
val allInputFields = ViewHierarchyNavigator.getAllInputFields(groupAncestor ?: context?.rootView)
val currentIndex = allInputFields.indexOf(newFocus)

context.emitEvent(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.reactnativekeyboardcontroller.managers

import com.facebook.react.uimanager.ThemedReactContext
import com.reactnativekeyboardcontroller.views.KeyboardToolbarGroupReactViewGroup

class KeyboardToolbarGroupViewManagerImpl {
fun createViewInstance(reactContext: ThemedReactContext): KeyboardToolbarGroupReactViewGroup =
KeyboardToolbarGroupReactViewGroup(reactContext)

companion object {
const val NAME = "KeyboardToolbarGroupView"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.KeyboardToolbarGroupReactViewGroup

object ViewHierarchyNavigator {
fun setFocusTo(
Expand All @@ -25,19 +26,40 @@ 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 KeyboardToolbarGroupReactViewGroup) {
for (i in 0 until view.childCount) {
findEditTexts(view.getChildAt(i))
}
}
}

// Start the search with the provided viewGroup
findEditTexts(viewGroup)
// If the root is a group itself, search within it (for group-scoped queries)
if (viewGroup is KeyboardToolbarGroupReactViewGroup) {
for (i in 0 until viewGroup.childCount) {
findEditTexts(viewGroup.getChildAt(i))
}
} else {
findEditTexts(viewGroup)
}

return editTexts
}

/**
* Finds the closest [KeyboardToolbarGroupReactViewGroup] ancestor of the given view.
* Returns null if the view is not inside any group.
*/
fun findGroupAncestor(view: View?): KeyboardToolbarGroupReactViewGroup? {
var current = view?.parent
while (current != null) {
if (current is KeyboardToolbarGroupReactViewGroup) {
return current
}
current = current.parent
}
return null
}

private fun findNextEditText(currentFocus: View): EditText? = findEditTextInDirection(currentFocus, 1)

private fun findPreviousEditText(currentFocus: View): EditText? = findEditTextInDirection(currentFocus, -1)
Expand All @@ -64,6 +86,11 @@ object ViewHierarchyNavigator {
i += direction
}

// Don't navigate outside the group boundary
if (parentViewGroup is KeyboardToolbarGroupReactViewGroup) {
return null
}

// Recurse to the parent's parent if no sibling EditText is found
return findEditTextInDirection(parentViewGroup, direction)
}
Expand Down Expand Up @@ -91,7 +118,7 @@ object ViewHierarchyNavigator {

if (isValidTextInput(child)) {
result = child as EditText
} else if (child is ViewGroup) {
} else if (child is ViewGroup && child !is KeyboardToolbarGroupReactViewGroup) {
// If the child is a ViewGroup, check its children recursively
result = findEditTextInHierarchy(child, direction)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.reactnativekeyboardcontroller.views

import android.annotation.SuppressLint
import android.content.Context
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.views.view.ReactViewGroup

@SuppressLint("ViewConstructor")
class KeyboardToolbarGroupReactViewGroup : ReactViewGroup {
constructor(reactContext: ThemedReactContext) : super(reactContext)
internal constructor(context: Context) : super(context)
// semantic view used in KeyboardToolbar traverse algorithm
}
1 change: 1 addition & 0 deletions android/src/main/jni/RNKC.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <react/renderer/components/RNKC/RNKCOverKeyboardViewComponentDescriptor.h>
#include <react/renderer/components/RNKC/RNKCKeyboardBackgroundViewComponentDescriptor.h>
#include <react/renderer/components/RNKC/RNKCClippingScrollViewDecoratorViewComponentDescriptor.h>
#include <react/renderer/components/RNKC/RNKCKeyboardToolbarGroupViewComponentDescriptor.h>

#include <memory>
#include <string>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.reactnativekeyboardcontroller

import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.views.view.ReactViewManager
import com.reactnativekeyboardcontroller.managers.KeyboardToolbarGroupViewManagerImpl
import com.reactnativekeyboardcontroller.views.KeyboardToolbarGroupReactViewGroup

class KeyboardToolbarGroupViewManager : ReactViewManager() {
private val manager = KeyboardToolbarGroupViewManagerImpl()

override fun getName(): String = KeyboardToolbarGroupViewManagerImpl.NAME

override fun createViewInstance(reactContext: ThemedReactContext): KeyboardToolbarGroupReactViewGroup =
manager.createViewInstance(reactContext)
}
Loading
Loading