├── .editorconfig ├── .gitattributes ├── .github ├── actions │ └── setup │ │ └── action.yml ├── images │ ├── rnek-android-example.gif │ └── rnek-ios-example.gif └── workflows │ └── ci.yml ├── .gitignore ├── .nvmrc ├── .watchmanconfig ├── .yarn └── patches │ └── react-native-builder-bob-npm-0.35.2-4e84215963.patch ├── .yarnrc.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── android ├── build.gradle ├── gradle.properties └── src │ ├── main │ ├── AndroidManifest.xml │ ├── AndroidManifestNew.xml │ └── java │ │ └── com │ │ └── externalkeyboard │ │ ├── ExternalKeyboardViewPackage.java │ │ ├── events │ │ ├── EventHelper.java │ │ ├── FocusChangeEvent.java │ │ ├── KeyPressDownEvent.java │ │ ├── KeyPressUpEvent.java │ │ └── MultiplyTextSubmit.java │ │ ├── helper │ │ └── FocusHelper.java │ │ ├── modules │ │ └── ExternalKeyboardModule.java │ │ ├── services │ │ ├── KeyboardKeyPressHandler.java │ │ └── KeyboardService.java │ │ └── views │ │ ├── ExternalKeyboardView │ │ ├── ExternalKeyboardView.java │ │ └── ExternalKeyboardViewManager.java │ │ ├── KeyboardFocusGroup │ │ ├── KeyboardFocusGroup.java │ │ └── KeyboardFocusGroupManager.java │ │ └── TextInputFocusWrapper │ │ ├── TextInputFocusWrapper.java │ │ └── TextInputFocusWrapperManager.java │ ├── newarch │ ├── ExternalKeyboardModuleSpec.java │ ├── ExternalKeyboardViewManagerSpec.java │ ├── KeyboardFocusGroupManagerSpec.java │ └── TextInputFocusWrapperManagerSpec.java │ └── oldarch │ ├── ExternalKeyboardModuleSpec.java │ ├── ExternalKeyboardViewManagerSpec.java │ ├── KeyboardFocusGroupManagerSpec.java │ └── TextInputFocusWrapperManagerSpec.java ├── babel.config.js ├── example ├── .bundle │ └── config ├── .node-version ├── .watchmanconfig ├── .yarnrc.yml ├── Gemfile ├── Gemfile.lock ├── android │ ├── app │ │ ├── build.gradle │ │ ├── debug.keystore │ │ ├── proguard-rules.pro │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ └── externalkeyboard │ │ │ │ └── example │ │ │ │ ├── MainActivity.kt │ │ │ │ └── MainApplication.kt │ │ │ └── res │ │ │ ├── drawable │ │ │ └── rn_edit_text_material.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ └── values │ │ │ ├── strings.xml │ │ │ └── styles.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ └── settings.gradle ├── app.json ├── babel.config.js ├── index.js ├── ios │ ├── .xcode.env │ ├── ExternalKeyboardExample-Bridging-Header.h │ ├── ExternalKeyboardExample.xcodeproj │ │ ├── project.pbxproj │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── ExternalKeyboardExample.xcscheme │ ├── ExternalKeyboardExample.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ ├── ExternalKeyboardExample │ │ ├── AppDelegate.h │ │ ├── AppDelegate.mm │ │ ├── Images.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ ├── Info.plist │ │ ├── LaunchScreen.storyboard │ │ ├── PrivacyInfo.xcprivacy │ │ └── main.m │ ├── ExternalKeyboardExampleTests │ │ ├── ExternalKeyboardExampleTests.m │ │ └── Info.plist │ ├── File.swift │ ├── Podfile │ └── Podfile.lock ├── metro.config.js ├── package-lock.json ├── package.json ├── react-native.config.js └── src │ ├── App.tsx │ ├── BaseExample.tsx │ ├── ModalExample.tsx │ ├── components │ ├── Cats │ │ ├── Cats.tsx │ │ ├── FocusableImage.tsx │ │ └── assets │ │ │ ├── 01.png │ │ │ ├── 02.png │ │ │ ├── 03.png │ │ │ ├── 06.png │ │ │ ├── 07.png │ │ │ ├── 08.png │ │ │ ├── 09.png │ │ │ ├── 10.png │ │ │ ├── 11.png │ │ │ ├── 12.png │ │ │ ├── 13.png │ │ │ ├── 14.png │ │ │ ├── 15.png │ │ │ ├── 16.png │ │ │ ├── 17.png │ │ │ ├── 18.png │ │ │ ├── 19.png │ │ │ └── 20.png │ ├── ComponentsExample │ │ └── ComponentsExample.tsx │ ├── ContrastColors │ │ ├── Color │ │ │ └── Color.tsx │ │ ├── ContrastColors.tsx │ │ └── FocusGroupExample.tsx │ └── LineButton │ │ └── LineButton.tsx │ └── screens │ └── Home │ └── Home.tsx ├── ios ├── Delegates │ ├── RNCEKVFocusDelegate │ │ ├── RNCEKVFocusDelegate.h │ │ ├── RNCEKVFocusDelegate.mm │ │ └── RNCEKVFocusProtocol.h │ ├── RNCEKVGroupIdentifierDelegate │ │ ├── RNCEKVGroupIdentifierDelegate.h │ │ ├── RNCEKVGroupIdentifierDelegate.mm │ │ └── RNCEKVGroupIdentifierProtocol.h │ └── RNCEKVHaloDelegate │ │ ├── RNCEKVHaloDelegate.h │ │ ├── RNCEKVHaloDelegate.mm │ │ └── RNCEKVHaloProtocol.h ├── Extensions │ ├── RCTTextInputComponentView+RNCEKVExternalKeyboard.h │ ├── RCTTextInputComponentView+RNCEKVExternalKeyboard.mm │ ├── UIViewController+RNCEKVExternalKeyboard.h │ └── UIViewController+RNCEKVExternalKeyboard.mm ├── Modules │ ├── RNCEKVExternalKeyboardModule.h │ └── RNCEKVExternalKeyboardModule.mm └── Views │ ├── RNCEKVExternalKeyboardView │ ├── Helpers │ │ ├── RNCEKVFabricEventHelper │ │ │ ├── RNCEKVFabricEventHelper.h │ │ │ └── RNCEKVFabricEventHelper.mm │ │ ├── RNCEKVFocusEffectUtility │ │ │ ├── RNCEKVFocusEffectUtility.h │ │ │ └── RNCEKVFocusEffectUtility.mm │ │ └── RNCEKVKeyboardKeyPressHandler │ │ │ ├── RNCEKVKeyboardKeyPressHandler.h │ │ │ └── RNCEKVKeyboardKeyPressHandler.mm │ ├── RNCEKVExternalKeyboardView.h │ ├── RNCEKVExternalKeyboardView.mm │ ├── RNCEKVExternalKeyboardViewManager.h │ └── RNCEKVExternalKeyboardViewManager.mm │ ├── RNCEKVKeyboardFocusGroupView │ ├── RNCEKVKeyboardFocusGroup.h │ ├── RNCEKVKeyboardFocusGroup.mm │ ├── RNCEKVKeyboardFocusGroupManager.h │ └── RNCEKVKeyboardFocusGroupManager.mm │ └── RNCEKVTextInputFocusWrapper │ ├── RNCEKVTextInputFocusWrapper.h │ ├── RNCEKVTextInputFocusWrapper.mm │ ├── RNCEKVTextInputFocusWrapperManager.h │ └── RNCEKVTextInputFocusWrapperManager.mm ├── lefthook.yml ├── package-lock.json ├── package.json ├── react-native-external-keyboard.podspec ├── react-native.config.js ├── scripts └── bootstrap.js ├── src ├── __tests__ │ └── index.test.tsx ├── components │ ├── BaseKeyboardView │ │ ├── BaseKeyboardView.hooks.ts │ │ └── BaseKeyboardView.tsx │ ├── KeyboardExtendedInput │ │ └── KeyboardExtendedInput.tsx │ ├── KeyboardFocusGroup │ │ ├── KeyboardFocusGroup.ios.tsx │ │ └── KeyboardFocusGroup.tsx │ ├── KeyboardFocusView │ │ ├── KeyboardFocusView.tsx │ │ ├── hooks │ │ │ ├── index.ts │ │ │ └── useFocusStyle │ │ │ │ ├── index.ts │ │ │ │ ├── useFocusStyle.ts │ │ │ │ └── useTintStyle.ts │ │ └── index.ts │ ├── RenderPropComponent │ │ └── RenderPropComponent.tsx │ ├── Touchable │ │ └── Pressable.tsx │ └── index.ts ├── context │ ├── BubbledKeyPressContext.ts │ ├── GroupIdentifierContext.ts │ └── IsViewFocusedContext.ts ├── index.tsx ├── modules │ ├── Keyboard.android.ts │ └── Keyboard.ts ├── nativeSpec │ ├── ExternalKeyboardViewNativeComponent.ts │ ├── KeyboardFocusGroupNativeComponent.ts │ ├── NativeExternalKeyboardModule.ts │ ├── TextInputFocusWrapperNativeComponent.ts │ └── index.ts ├── types │ ├── BaseKeyboardView.ts │ ├── FocusStyle.ts │ ├── KeyboardFocusView.types.ts │ ├── WithKeyboardFocus.ts │ └── index.ts └── utils │ ├── focusEventMapper.tsx │ ├── useFocusStyle.tsx │ ├── useKeyboardPress │ ├── useKeyboardPress.android.ts │ ├── useKeyboardPress.ios.ts │ ├── useKeyboardPress.ts │ └── useKeyboardPress.types.ts │ ├── useOnFocusChange.ts │ └── withKeyboardFocus.tsx ├── tsconfig.build.json ├── tsconfig.json ├── turbo.json └── types └── index.d.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | 9 | indent_style = space 10 | indent_size = 2 11 | 12 | end_of_line = lf 13 | charset = utf-8 14 | trim_trailing_whitespace = true 15 | insert_final_newline = true 16 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text 2 | # specific for windows script files 3 | *.bat text eol=crlf -------------------------------------------------------------------------------- /.github/actions/setup/action.yml: -------------------------------------------------------------------------------- 1 | name: Setup 2 | description: Setup Node.js and install dependencies 3 | 4 | runs: 5 | using: composite 6 | steps: 7 | - name: Setup Node.js 8 | uses: actions/setup-node@v3 9 | with: 10 | node-version-file: .nvmrc 11 | 12 | - name: Cache dependencies 13 | id: yarn-cache 14 | uses: actions/cache@v3 15 | with: 16 | path: | 17 | **/node_modules 18 | .yarn/install-state.gz 19 | key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }}-${{ hashFiles('**/package.json', '!node_modules/**') }} 20 | restore-keys: | 21 | ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }} 22 | ${{ runner.os }}-yarn- 23 | 24 | - name: Install dependencies 25 | if: steps.yarn-cache.outputs.cache-hit != 'true' 26 | run: yarn install --immutable 27 | shell: bash 28 | -------------------------------------------------------------------------------- /.github/images/rnek-android-example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArturKalach/react-native-external-keyboard/6d4ae711800c15f095f088fadfb03ec2ceb177f8/.github/images/rnek-android-example.gif -------------------------------------------------------------------------------- /.github/images/rnek-ios-example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArturKalach/react-native-external-keyboard/6d4ae711800c15f095f088fadfb03ec2ceb177f8/.github/images/rnek-ios-example.gif -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # XDE 6 | .expo/ 7 | 8 | # VSCode 9 | .vscode/ 10 | jsconfig.json 11 | 12 | # Xcode 13 | # 14 | build/ 15 | *.pbxuser 16 | !default.pbxuser 17 | *.mode1v3 18 | !default.mode1v3 19 | *.mode2v3 20 | !default.mode2v3 21 | *.perspectivev3 22 | !default.perspectivev3 23 | xcuserdata 24 | *.xccheckout 25 | *.moved-aside 26 | DerivedData 27 | *.hmap 28 | *.ipa 29 | *.xcuserstate 30 | project.xcworkspace 31 | .xcode.env.local 32 | 33 | # Android/IJ 34 | # 35 | .classpath 36 | .cxx 37 | .gradle 38 | .idea 39 | .project 40 | .settings 41 | local.properties 42 | android.iml 43 | 44 | # Cocoapods 45 | # 46 | example/ios/Pods 47 | 48 | # Ruby 49 | example/vendor/ 50 | 51 | # node.js 52 | # 53 | node_modules/ 54 | npm-debug.log 55 | yarn-debug.log 56 | yarn-error.log 57 | # Yarn 58 | .yarn/* 59 | !.yarn/patches 60 | !.yarn/plugins 61 | !.yarn/releases 62 | !.yarn/sdks 63 | !.yarn/versions 64 | 65 | example/.yarn 66 | 67 | # BUCK 68 | buck-out/ 69 | \.buckd/ 70 | android/app/libs 71 | android/keystores/debug.keystore 72 | 73 | # Expo 74 | .expo/ 75 | 76 | # Turborepo 77 | .turbo/ 78 | 79 | # generated by bob 80 | lib/ 81 | 82 | # React Native Codegen 83 | ios/generated 84 | android/generated 85 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v18 -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /.yarn/patches/react-native-builder-bob-npm-0.35.2-4e84215963.patch: -------------------------------------------------------------------------------- 1 | diff --git a/lib/utils/patchCodegenAndroidPackage.js b/lib/utils/patchCodegenAndroidPackage.js 2 | index 2c5165815c37daa008ff6a7c246a44d51ba5f963..9ac208c458f517274d1e4f3d5c2cb9f034b2afa5 100644 3 | --- a/lib/utils/patchCodegenAndroidPackage.js 4 | +++ b/lib/utils/patchCodegenAndroidPackage.js 5 | @@ -57,8 +57,22 @@ packageJson, report) { 6 | const newFilePath = _path.default.resolve(newPackagePath, file); 7 | await _fsExtra.default.rename(filePath, newFilePath); 8 | })); 9 | - await _fsExtra.default.rm(_path.default.resolve(codegenAndroidPath, 'java/com/facebook'), { 10 | - recursive: true 11 | - }); 12 | + if ( 13 | + await _fsExtra.default.pathExists( 14 | + _path.default.resolve(codegenAndroidPath, 'java/com/facebook/react/viewmanagers') 15 | + ) 16 | + ) { 17 | + // Keep the view managers 18 | + await _fsExtra.default.rm( 19 | + _path.default.resolve(codegenAndroidPath, 'java/com/facebook/fbreact'), 20 | + { recursive: true } 21 | + ); 22 | + } else { 23 | + // Delete the entire facebook namespace 24 | + await _fsExtra.default.rm( 25 | + _path.default.resolve(codegenAndroidPath, 'java/com/facebook'), 26 | + { recursive: true } 27 | + ); 28 | + } 29 | } 30 | //# sourceMappingURL=patchCodegenAndroidPackage.js.map 31 | \ No newline at end of file 32 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Artur Kalach 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | // Buildscript is evaluated before everything else so we can't use getExtOrDefault 3 | def kotlin_version = rootProject.ext.has("kotlinVersion") ? rootProject.ext.get("kotlinVersion") : project.properties["ExternalKeyboard_kotlinVersion"] 4 | 5 | repositories { 6 | google() 7 | mavenCentral() 8 | } 9 | 10 | dependencies { 11 | classpath "com.android.tools.build:gradle:7.2.1" 12 | // noinspection DifferentKotlinGradleVersion 13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 14 | } 15 | } 16 | 17 | def reactNativeArchitectures() { 18 | def value = rootProject.getProperties().get("reactNativeArchitectures") 19 | return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"] 20 | } 21 | 22 | def isNewArchitectureEnabled() { 23 | return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true" 24 | } 25 | 26 | apply plugin: "com.android.library" 27 | apply plugin: "kotlin-android" 28 | 29 | if (isNewArchitectureEnabled()) { 30 | apply plugin: "com.facebook.react" 31 | } 32 | 33 | def getExtOrDefault(name) { 34 | return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["ExternalKeyboard_" + name] 35 | } 36 | 37 | def getExtOrIntegerDefault(name) { 38 | return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["ExternalKeyboard_" + name]).toInteger() 39 | } 40 | 41 | def supportsNamespace() { 42 | def parsed = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.') 43 | def major = parsed[0].toInteger() 44 | def minor = parsed[1].toInteger() 45 | 46 | // Namespace support was added in 7.3.0 47 | return (major == 7 && minor >= 3) || major >= 8 48 | } 49 | 50 | android { 51 | if (supportsNamespace()) { 52 | namespace "com.externalkeyboard" 53 | 54 | sourceSets { 55 | main { 56 | manifest.srcFile "src/main/AndroidManifestNew.xml" 57 | } 58 | } 59 | } 60 | 61 | compileSdkVersion getExtOrIntegerDefault("compileSdkVersion") 62 | 63 | defaultConfig { 64 | minSdkVersion getExtOrIntegerDefault("minSdkVersion") 65 | targetSdkVersion getExtOrIntegerDefault("targetSdkVersion") 66 | buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString() 67 | 68 | } 69 | 70 | buildFeatures { 71 | buildConfig true 72 | } 73 | 74 | buildTypes { 75 | release { 76 | minifyEnabled false 77 | } 78 | } 79 | 80 | lintOptions { 81 | disable "GradleCompatible" 82 | } 83 | 84 | compileOptions { 85 | sourceCompatibility JavaVersion.VERSION_1_8 86 | targetCompatibility JavaVersion.VERSION_1_8 87 | } 88 | 89 | sourceSets { 90 | main { 91 | if (isNewArchitectureEnabled()) { 92 | java.srcDirs += [ 93 | "src/newarch", 94 | "generated/java", 95 | "generated/jni" 96 | ] 97 | } else { 98 | java.srcDirs += ["src/oldarch"] 99 | } 100 | } 101 | } 102 | } 103 | 104 | repositories { 105 | mavenCentral() 106 | google() 107 | } 108 | 109 | def kotlin_version = getExtOrDefault("kotlinVersion") 110 | 111 | dependencies { 112 | // For < 0.71, this will be from the local maven repo 113 | // For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin 114 | //noinspection GradleDynamicVersion 115 | implementation "com.facebook.react:react-native:+" 116 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 117 | } 118 | 119 | if (isNewArchitectureEnabled()) { 120 | react { 121 | jsRootDir = file("../src/") 122 | libraryName = "ExternalKeyboardView" 123 | codegenJavaPackageName = "com.externalkeyboard" 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | ExternalKeyboard_kotlinVersion=1.7.0 2 | ExternalKeyboard_minSdkVersion=21 3 | ExternalKeyboard_targetSdkVersion=31 4 | ExternalKeyboard_compileSdkVersion=31 5 | ExternalKeyboard_ndkversion=21.4.7075529 6 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifestNew.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /android/src/main/java/com/externalkeyboard/ExternalKeyboardViewPackage.java: -------------------------------------------------------------------------------- 1 | package com.externalkeyboard; 2 | 3 | 4 | import com.externalkeyboard.modules.ExternalKeyboardModule; 5 | import com.externalkeyboard.views.ExternalKeyboardView.ExternalKeyboardViewManager; 6 | import com.externalkeyboard.views.KeyboardFocusGroup.KeyboardFocusGroupManager; 7 | import com.externalkeyboard.views.TextInputFocusWrapper.TextInputFocusWrapperManager; 8 | import com.facebook.react.TurboReactPackage; 9 | import com.facebook.react.bridge.NativeModule; 10 | import com.facebook.react.bridge.ReactApplicationContext; 11 | import com.facebook.react.module.model.ReactModuleInfo; 12 | import com.facebook.react.module.model.ReactModuleInfoProvider; 13 | import com.facebook.react.uimanager.ViewManager; 14 | 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | import java.util.HashMap; 18 | import java.util.Map; 19 | 20 | public class ExternalKeyboardViewPackage extends TurboReactPackage { 21 | @Override 22 | public NativeModule getModule(String name, ReactApplicationContext reactContext) { 23 | if (name.equals(ExternalKeyboardModule.NAME)) { 24 | return new ExternalKeyboardModule(reactContext); 25 | } else { 26 | return null; 27 | } 28 | } 29 | 30 | @Override 31 | public ReactModuleInfoProvider getReactModuleInfoProvider() { 32 | return () -> { 33 | Map map = new HashMap<>(); 34 | boolean isTurboModule = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED; 35 | map.put(ExternalKeyboardModule.NAME, new ReactModuleInfo(ExternalKeyboardModule.NAME, // name 36 | ExternalKeyboardModule.NAME, // className 37 | false, // canOverrideExistingModule 38 | false, // needsEagerInit 39 | true, // hasConstants 40 | false, // isCxxModule 41 | isTurboModule // isTurboModule 42 | )); 43 | return map; 44 | }; 45 | } 46 | 47 | @Override 48 | public List createViewManagers(ReactApplicationContext reactContext) { 49 | List viewManagers = new ArrayList<>(); 50 | viewManagers.add(new ExternalKeyboardViewManager()); 51 | viewManagers.add(new TextInputFocusWrapperManager()); 52 | viewManagers.add(new KeyboardFocusGroupManager()); 53 | 54 | return viewManagers; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /android/src/main/java/com/externalkeyboard/events/EventHelper.java: -------------------------------------------------------------------------------- 1 | package com.externalkeyboard.events; 2 | 3 | import android.view.KeyEvent; 4 | 5 | import com.facebook.react.bridge.ReactContext; 6 | import com.facebook.react.uimanager.UIManagerHelper; 7 | import com.facebook.react.uimanager.events.EventDispatcher; 8 | 9 | public class EventHelper { 10 | public static void focusChanged(ReactContext context, int id, boolean hasFocus) { 11 | int surfaceId = UIManagerHelper.getSurfaceId(context); 12 | FocusChangeEvent event = new FocusChangeEvent(surfaceId, id, hasFocus); 13 | EventDispatcher eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(context, id); 14 | if (eventDispatcher != null) { 15 | eventDispatcher.dispatchEvent(event); 16 | } 17 | } 18 | 19 | public static void pressDown(ReactContext context, int id, int keyCode, KeyEvent keyEvent) { 20 | int surfaceId = UIManagerHelper.getSurfaceId(context); 21 | KeyPressDownEvent keyPressDownEvent = new KeyPressDownEvent(surfaceId, id, keyCode, keyEvent); 22 | EventDispatcher eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(context, id); 23 | if (eventDispatcher != null) { 24 | eventDispatcher.dispatchEvent(keyPressDownEvent); 25 | } 26 | } 27 | 28 | public static void pressUp(ReactContext context, int id, int keyCode, KeyEvent keyEvent, boolean isLongPress) { 29 | int surfaceId = UIManagerHelper.getSurfaceId(context); 30 | KeyPressUpEvent keyPressUpEvent = new KeyPressUpEvent(surfaceId, id, keyCode, keyEvent, isLongPress); 31 | EventDispatcher eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(context, id); 32 | if (eventDispatcher != null) { 33 | eventDispatcher.dispatchEvent(keyPressUpEvent); 34 | } 35 | } 36 | 37 | public static void multiplyTextSubmit(ReactContext context, int id, String text) { 38 | int surfaceId = UIManagerHelper.getSurfaceId(context); 39 | MultiplyTextSubmit event = new MultiplyTextSubmit(surfaceId, id, text); 40 | EventDispatcher eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(context, id); 41 | if (eventDispatcher != null) { 42 | eventDispatcher.dispatchEvent(event); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /android/src/main/java/com/externalkeyboard/events/FocusChangeEvent.java: -------------------------------------------------------------------------------- 1 | package com.externalkeyboard.events; 2 | 3 | import com.facebook.react.bridge.Arguments; 4 | import com.facebook.react.bridge.WritableMap; 5 | import com.facebook.react.uimanager.events.Event; 6 | 7 | public class FocusChangeEvent extends Event { 8 | public static String EVENT_NAME = "topFocusChange"; 9 | public WritableMap payload; 10 | 11 | public FocusChangeEvent(int surfaceId, int id, Boolean hasFocus) { 12 | super(surfaceId, id); 13 | payload = Arguments.createMap(); 14 | payload.putBoolean("isFocused", hasFocus); 15 | } 16 | 17 | @Override 18 | public String getEventName() { 19 | return EVENT_NAME; 20 | } 21 | 22 | @Override 23 | public WritableMap getEventData() { 24 | return payload; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /android/src/main/java/com/externalkeyboard/events/KeyPressDownEvent.java: -------------------------------------------------------------------------------- 1 | package com.externalkeyboard.events; 2 | 3 | import android.view.KeyEvent; 4 | 5 | import com.facebook.react.bridge.Arguments; 6 | import com.facebook.react.bridge.WritableMap; 7 | import com.facebook.react.uimanager.events.Event; 8 | 9 | public class KeyPressDownEvent extends Event { 10 | public static String EVENT_NAME = "topKeyDownPress"; 11 | public WritableMap payload; 12 | 13 | public KeyPressDownEvent(int surfaceId, int id, int keyCode, KeyEvent keyEvent) { 14 | super(surfaceId, id); 15 | 16 | int unicode = keyEvent.getUnicodeChar(); 17 | payload = Arguments.createMap(); 18 | payload.putInt("keyCode", keyCode); 19 | payload.putInt("unicode", unicode); 20 | payload.putString("unicodeChar", String.valueOf((char) unicode)); 21 | payload.putBoolean("isLongPress", keyEvent.isLongPress()); 22 | payload.putBoolean("isAltPressed", keyEvent.isAltPressed()); 23 | payload.putBoolean("isShiftPressed", keyEvent.isShiftPressed()); 24 | payload.putBoolean("isCtrlPressed", keyEvent.isCtrlPressed()); 25 | payload.putBoolean("isCapsLockOn", keyEvent.isCapsLockOn()); 26 | payload.putBoolean("hasNoModifiers", keyEvent.hasNoModifiers()); 27 | } 28 | 29 | @Override 30 | public String getEventName() { 31 | return EVENT_NAME; 32 | } 33 | 34 | @Override 35 | public WritableMap getEventData() { 36 | return payload; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /android/src/main/java/com/externalkeyboard/events/KeyPressUpEvent.java: -------------------------------------------------------------------------------- 1 | package com.externalkeyboard.events; 2 | 3 | import android.view.KeyEvent; 4 | 5 | import com.facebook.react.bridge.Arguments; 6 | import com.facebook.react.bridge.WritableMap; 7 | import com.facebook.react.uimanager.events.Event; 8 | 9 | public class KeyPressUpEvent extends Event { 10 | public static String EVENT_NAME = "topKeyUpPress"; 11 | public WritableMap payload; 12 | 13 | public KeyPressUpEvent(int surfaceId, int id, int keyCode, KeyEvent keyEvent, boolean isLongPress) { 14 | super(surfaceId, id); 15 | 16 | int unicode = keyEvent.getUnicodeChar(); 17 | payload = Arguments.createMap(); 18 | payload.putInt("keyCode", keyCode); 19 | payload.putInt("unicode", unicode); 20 | payload.putString("unicodeChar", String.valueOf((char) unicode)); 21 | payload.putBoolean("isLongPress", isLongPress); 22 | payload.putBoolean("isAltPressed", keyEvent.isAltPressed()); 23 | payload.putBoolean("isShiftPressed", keyEvent.isShiftPressed()); 24 | payload.putBoolean("isCtrlPressed", keyEvent.isCtrlPressed()); 25 | payload.putBoolean("isCapsLockOn", keyEvent.isCapsLockOn()); 26 | payload.putBoolean("hasNoModifiers", keyEvent.hasNoModifiers()); 27 | } 28 | 29 | @Override 30 | public String getEventName() { 31 | return EVENT_NAME; 32 | } 33 | 34 | @Override 35 | public WritableMap getEventData() { 36 | return payload; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /android/src/main/java/com/externalkeyboard/events/MultiplyTextSubmit.java: -------------------------------------------------------------------------------- 1 | package com.externalkeyboard.events; 2 | 3 | import com.facebook.react.bridge.Arguments; 4 | import com.facebook.react.bridge.WritableMap; 5 | import com.facebook.react.uimanager.events.Event; 6 | 7 | public class MultiplyTextSubmit extends Event { 8 | public static String EVENT_NAME = "topMultiplyTextSubmit"; 9 | public WritableMap payload; 10 | 11 | public MultiplyTextSubmit(int surfaceId, int id, String text) { 12 | super(surfaceId, id); 13 | payload = Arguments.createMap(); 14 | payload.putString("text", text); 15 | } 16 | 17 | @Override 18 | public String getEventName() { 19 | return EVENT_NAME; 20 | } 21 | 22 | @Override 23 | public WritableMap getEventData() { 24 | return payload; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /android/src/main/java/com/externalkeyboard/helper/FocusHelper.java: -------------------------------------------------------------------------------- 1 | package com.externalkeyboard.helper; 2 | 3 | import android.view.View; 4 | import android.view.ViewGroup; 5 | 6 | public class FocusHelper { 7 | public static View getFocusableView(ViewGroup viewGroup) { 8 | if (viewGroup.getChildCount() > 0) { 9 | View subView = viewGroup.getChildAt(0); 10 | if (subView.isFocusable()) { 11 | return subView; 12 | } else if (subView instanceof ViewGroup) { 13 | return getFocusableView((ViewGroup) subView); 14 | } 15 | } 16 | 17 | return null; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /android/src/main/java/com/externalkeyboard/modules/ExternalKeyboardModule.java: -------------------------------------------------------------------------------- 1 | package com.externalkeyboard.modules; 2 | 3 | import android.content.Context; 4 | import android.view.View; 5 | import android.view.inputmethod.InputMethodManager; 6 | 7 | import com.externalkeyboard.ExternalKeyboardModuleSpec; 8 | import com.facebook.react.bridge.Promise; 9 | import com.facebook.react.bridge.ReactApplicationContext; 10 | import com.facebook.react.bridge.ReactMethod; 11 | 12 | public class ExternalKeyboardModule extends ExternalKeyboardModuleSpec { 13 | public static final String NAME = "ExternalKeyboardModule"; 14 | private static View focusedView = null; 15 | private final ReactApplicationContext context; 16 | 17 | 18 | private boolean dismiss() { 19 | if(focusedView == null) return false; 20 | try { 21 | InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); 22 | if (imm != null) { 23 | imm.hideSoftInputFromWindow(focusedView.getWindowToken(), 0); 24 | return true; 25 | } 26 | } catch (Exception ignored){} 27 | 28 | return false; 29 | } 30 | 31 | 32 | public static void setFocusedTextInput (View _focusedView) { 33 | focusedView = _focusedView; 34 | } 35 | 36 | public ExternalKeyboardModule(ReactApplicationContext reactContext) { 37 | super(reactContext); 38 | context = reactContext; 39 | } 40 | 41 | @Override 42 | public String getName() { 43 | return NAME; 44 | } 45 | 46 | @Override 47 | @ReactMethod 48 | public void dismissKeyboard(Promise promise) { 49 | boolean result = dismiss(); 50 | promise.resolve(result); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /android/src/main/java/com/externalkeyboard/services/KeyboardKeyPressHandler.java: -------------------------------------------------------------------------------- 1 | package com.externalkeyboard.services; 2 | 3 | import android.os.Build; 4 | import android.view.KeyEvent; 5 | 6 | import java.util.HashMap; 7 | 8 | public class KeyboardKeyPressHandler { 9 | private final HashMap pressedKeys = new HashMap(); 10 | private final HashMap longPress = new HashMap(); 11 | 12 | private boolean actionDownHandler(int keyCode, KeyEvent keyEvent) { 13 | boolean wasAlreadyPressed = false; 14 | if (pressedKeys.containsKey(keyCode)) { 15 | wasAlreadyPressed = pressedKeys.get(keyCode); 16 | } 17 | pressedKeys.put(keyCode, true); 18 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ECLAIR && keyEvent.isLongPress()) { 19 | longPress.put(keyCode, keyEvent.isLongPress()); 20 | } 21 | 22 | return !wasAlreadyPressed; 23 | } 24 | 25 | private boolean actionUpHandler(int keyCode, KeyEvent keyEvent) { 26 | pressedKeys.put(keyCode, false); 27 | 28 | boolean isLongPress = false; 29 | if (longPress.containsKey(keyCode)) { 30 | isLongPress = longPress.get(keyCode); 31 | } 32 | longPress.put(keyCode, false); 33 | 34 | return isLongPress; 35 | } 36 | 37 | public PressInfo getEventsFromKeyPress(int keyCode, KeyEvent keyEvent) { 38 | PressInfo pressInfo = new PressInfo(); 39 | 40 | if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) { 41 | pressInfo.firePressDownEvent = actionDownHandler(keyCode, keyEvent); 42 | } 43 | if (keyEvent.getAction() == KeyEvent.ACTION_UP) { 44 | pressInfo.firePressUpEvent = true; 45 | pressInfo.isLongPress = actionUpHandler(keyCode, keyEvent); 46 | } 47 | 48 | return pressInfo; 49 | } 50 | 51 | public class PressInfo { 52 | public boolean firePressDownEvent = false; 53 | public boolean firePressUpEvent = false; 54 | public boolean isLongPress = false; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /android/src/main/java/com/externalkeyboard/services/KeyboardService.java: -------------------------------------------------------------------------------- 1 | package com.externalkeyboard.services; 2 | 3 | 4 | import static com.facebook.react.uimanager.common.UIManagerType.FABRIC; 5 | 6 | import android.app.Activity; 7 | import android.util.Log; 8 | import android.view.View; 9 | 10 | import com.facebook.react.bridge.ReactApplicationContext; 11 | import com.facebook.react.bridge.UIManager; 12 | import com.facebook.react.uimanager.IllegalViewOperationException; 13 | import com.facebook.react.uimanager.UIManagerHelper; 14 | import com.facebook.react.uimanager.UIManagerModule; 15 | import com.facebook.react.uimanager.common.ViewUtil; 16 | 17 | public class KeyboardService { 18 | private final ReactApplicationContext context; 19 | 20 | public KeyboardService(ReactApplicationContext context) { 21 | this.context = context; 22 | } 23 | 24 | public void setKeyboardFocus(int tag) { 25 | final Activity activity = context.getCurrentActivity(); 26 | 27 | if (activity == null) { 28 | return; 29 | } 30 | 31 | activity.runOnUiThread(() -> { 32 | try { 33 | int uiManagerType = ViewUtil.getUIManagerType(tag); 34 | if (uiManagerType == FABRIC) { 35 | UIManager fabricUIManager = UIManagerHelper.getUIManager(context, uiManagerType); 36 | if (fabricUIManager != null) { 37 | View view = fabricUIManager.resolveView(tag); 38 | view.requestFocus(); 39 | } 40 | } else { 41 | UIManager uiManager = context.getNativeModule(UIManagerModule.class); 42 | View view = uiManager.resolveView(tag); 43 | view.requestFocus(); 44 | } 45 | } catch (IllegalViewOperationException error) { 46 | Log.e("KEYBOARD_FOCUS_ERROR", error.getMessage()); 47 | } 48 | }); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /android/src/main/java/com/externalkeyboard/views/KeyboardFocusGroup/KeyboardFocusGroup.java: -------------------------------------------------------------------------------- 1 | package com.externalkeyboard.views.KeyboardFocusGroup; 2 | 3 | import android.content.Context; 4 | 5 | import com.facebook.react.views.view.ReactViewGroup; 6 | 7 | public class KeyboardFocusGroup extends ReactViewGroup { 8 | public KeyboardFocusGroup(Context context) { 9 | super(context); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /android/src/main/java/com/externalkeyboard/views/KeyboardFocusGroup/KeyboardFocusGroupManager.java: -------------------------------------------------------------------------------- 1 | package com.externalkeyboard.views.KeyboardFocusGroup; 2 | 3 | import androidx.annotation.Nullable; 4 | 5 | import com.facebook.react.module.annotations.ReactModule; 6 | import com.facebook.react.uimanager.ThemedReactContext; 7 | import com.facebook.react.uimanager.annotations.ReactProp; 8 | 9 | @ReactModule(name = KeyboardFocusGroupManager.NAME) 10 | public class KeyboardFocusGroupManager extends com.externalkeyboard.KeyboardFocusGroupManagerSpec { 11 | public static final String NAME = "KeyboardFocusGroup"; 12 | 13 | @Override 14 | public String getName() { 15 | return NAME; 16 | } 17 | 18 | @Override 19 | public KeyboardFocusGroup createViewInstance(ThemedReactContext context) { 20 | return new KeyboardFocusGroup(context); 21 | } 22 | 23 | 24 | @Override 25 | @ReactProp(name = "tintColor") 26 | public void setTintColor(KeyboardFocusGroup view, @Nullable Integer value) { 27 | //stub 28 | } 29 | 30 | @Override 31 | @ReactProp(name = "groupIdentifier") 32 | public void setGroupIdentifier(KeyboardFocusGroup wrapper, String groupIdentifier) { 33 | //stub 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /android/src/main/java/com/externalkeyboard/views/TextInputFocusWrapper/TextInputFocusWrapperManager.java: -------------------------------------------------------------------------------- 1 | package com.externalkeyboard.views.TextInputFocusWrapper; 2 | 3 | import android.view.View; 4 | import android.view.ViewGroup; 5 | 6 | import androidx.annotation.NonNull; 7 | import androidx.annotation.Nullable; 8 | 9 | import com.externalkeyboard.events.FocusChangeEvent; 10 | import com.externalkeyboard.events.MultiplyTextSubmit; 11 | import com.externalkeyboard.views.ExternalKeyboardView.ExternalKeyboardView; 12 | import com.facebook.react.bridge.ReadableArray; 13 | import com.facebook.react.common.MapBuilder; 14 | import com.facebook.react.module.annotations.ReactModule; 15 | import com.facebook.react.uimanager.ThemedReactContext; 16 | import com.facebook.react.uimanager.annotations.ReactProp; 17 | import com.facebook.react.views.textinput.ReactEditText; 18 | import com.facebook.react.views.view.ReactViewGroup; 19 | 20 | import java.util.HashMap; 21 | import java.util.Map; 22 | 23 | 24 | @ReactModule(name = TextInputFocusWrapperManager.NAME) 25 | public class TextInputFocusWrapperManager extends com.externalkeyboard.TextInputFocusWrapperManagerSpec { 26 | public static final String NAME = "TextInputFocusWrapper"; 27 | 28 | @Override 29 | public String getName() { 30 | return NAME; 31 | } 32 | 33 | @Override 34 | public TextInputFocusWrapper createViewInstance(ThemedReactContext context) { 35 | return subscribeOnHierarchy(new TextInputFocusWrapper(context)); 36 | } 37 | 38 | @Override 39 | protected void addEventEmitters(final ThemedReactContext reactContext, TextInputFocusWrapper viewGroup) { 40 | viewGroup.subscribeOnFocus(); 41 | } 42 | 43 | protected TextInputFocusWrapper subscribeOnHierarchy(TextInputFocusWrapper viewGroup) { 44 | viewGroup.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() { 45 | @Override 46 | public void onChildViewAdded(View parent, View child) { 47 | if (child instanceof ReactEditText) { 48 | viewGroup.setEditText((ReactEditText) child); 49 | } 50 | } 51 | 52 | @Override 53 | public void onChildViewRemoved(View parent, View child) { 54 | if (child instanceof ReactEditText) { 55 | viewGroup.setEditText(null); 56 | } 57 | } 58 | }); 59 | 60 | return viewGroup; 61 | } 62 | 63 | @Override 64 | @ReactProp(name = "focusType") 65 | public void setFocusType(TextInputFocusWrapper view, int value) { 66 | view.setFocusType(value); 67 | } 68 | 69 | @Override 70 | @ReactProp(name = "blurType") 71 | public void setBlurType(TextInputFocusWrapper view, int value) { 72 | view.setBlurType(value); 73 | } 74 | 75 | @Override 76 | @ReactProp(name = "blurOnSubmit", defaultBoolean = true) 77 | public void setBlurOnSubmit(TextInputFocusWrapper view, boolean value) { 78 | view.setBlurOnSubmit(value); 79 | } 80 | 81 | @Override 82 | @ReactProp(name = "multiline") 83 | public void setMultiline(TextInputFocusWrapper view, boolean value) { 84 | view.setMultiline(value); 85 | } 86 | 87 | @Override 88 | public void setGroupIdentifier(TextInputFocusWrapper view, @Nullable String value) { 89 | //stub 90 | } 91 | 92 | 93 | @Override 94 | @ReactProp(name = "canBeFocused", defaultBoolean = true) 95 | public void setCanBeFocused(TextInputFocusWrapper view, boolean value) { 96 | view.setKeyboardFocusable(value); 97 | } 98 | 99 | @Override 100 | public void setHaloEffect(TextInputFocusWrapper view, boolean value) { 101 | //stub 102 | } 103 | 104 | @Override 105 | public void setTintColor(TextInputFocusWrapper view, @Nullable Integer value) { 106 | //stub 107 | } 108 | 109 | @Override 110 | public void onDropViewInstance(@NonNull TextInputFocusWrapper viewGroup) { 111 | viewGroup.setEditText(null); 112 | viewGroup.setOnFocusChangeListener(null); 113 | super.onDropViewInstance(viewGroup); 114 | } 115 | 116 | private Map createEventMap(String registrationName) { 117 | Map eventMap = new HashMap<>(); 118 | eventMap.put("registrationName", registrationName); 119 | return eventMap; 120 | } 121 | 122 | @Nullable 123 | @Override 124 | public Map getExportedCustomDirectEventTypeConstants() { 125 | Map export = new HashMap<>(); 126 | 127 | export.put(FocusChangeEvent.EVENT_NAME, createEventMap("onFocusChange")); 128 | export.put(MultiplyTextSubmit.EVENT_NAME, createEventMap("onMultiplyTextSubmit")); 129 | 130 | return export; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /android/src/newarch/ExternalKeyboardModuleSpec.java: -------------------------------------------------------------------------------- 1 | package com.externalkeyboard; 2 | 3 | import com.facebook.react.bridge.ReactApplicationContext; 4 | 5 | public abstract class ExternalKeyboardModuleSpec extends NativeExternalKeyboardModuleSpec { 6 | protected ExternalKeyboardModuleSpec(ReactApplicationContext context) { 7 | super(context); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /android/src/newarch/ExternalKeyboardViewManagerSpec.java: -------------------------------------------------------------------------------- 1 | package com.externalkeyboard; 2 | 3 | import com.facebook.react.viewmanagers.ExternalKeyboardViewManagerInterface; 4 | import com.facebook.react.views.view.ReactViewGroup; 5 | import com.facebook.react.views.view.ReactViewManager; 6 | 7 | public abstract class ExternalKeyboardViewManagerSpec extends ReactViewManager implements ExternalKeyboardViewManagerInterface { 8 | } 9 | -------------------------------------------------------------------------------- /android/src/newarch/KeyboardFocusGroupManagerSpec.java: -------------------------------------------------------------------------------- 1 | package com.externalkeyboard; 2 | 3 | import com.facebook.react.viewmanagers.KeyboardFocusGroupManagerInterface; 4 | import com.facebook.react.views.view.ReactViewGroup; 5 | import com.facebook.react.views.view.ReactViewManager; 6 | 7 | public abstract class KeyboardFocusGroupManagerSpec extends ReactViewManager implements KeyboardFocusGroupManagerInterface { 8 | } 9 | -------------------------------------------------------------------------------- /android/src/newarch/TextInputFocusWrapperManagerSpec.java: -------------------------------------------------------------------------------- 1 | package com.externalkeyboard; 2 | 3 | import android.view.ViewGroup; 4 | 5 | import androidx.annotation.Nullable; 6 | 7 | import com.facebook.react.uimanager.ViewGroupManager; 8 | import com.facebook.react.uimanager.ViewManagerDelegate; 9 | import com.facebook.react.viewmanagers.TextInputFocusWrapperManagerDelegate; 10 | import com.facebook.react.viewmanagers.TextInputFocusWrapperManagerInterface; 11 | 12 | public abstract class TextInputFocusWrapperManagerSpec extends ViewGroupManager implements TextInputFocusWrapperManagerInterface { 13 | private final ViewManagerDelegate mDelegate; 14 | 15 | public TextInputFocusWrapperManagerSpec() { 16 | mDelegate = new TextInputFocusWrapperManagerDelegate(this); 17 | } 18 | 19 | @Nullable 20 | @Override 21 | protected ViewManagerDelegate getDelegate() { 22 | return mDelegate; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /android/src/oldarch/ExternalKeyboardModuleSpec.java: -------------------------------------------------------------------------------- 1 | package com.externalkeyboard; 2 | 3 | import com.facebook.react.bridge.Promise; 4 | import com.facebook.react.bridge.ReactApplicationContext; 5 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 6 | 7 | public abstract class ExternalKeyboardModuleSpec extends ReactContextBaseJavaModule { 8 | protected ExternalKeyboardModuleSpec(ReactApplicationContext context) { 9 | super(context); 10 | } 11 | 12 | public abstract void dismissKeyboard(Promise promise); 13 | } 14 | -------------------------------------------------------------------------------- /android/src/oldarch/ExternalKeyboardViewManagerSpec.java: -------------------------------------------------------------------------------- 1 | package com.externalkeyboard; 2 | 3 | import android.view.ViewGroup; 4 | 5 | import androidx.annotation.Nullable; 6 | 7 | import com.externalkeyboard.views.ExternalKeyboardView.ExternalKeyboardView; 8 | import com.facebook.react.views.view.ReactViewManager; 9 | 10 | public abstract class ExternalKeyboardViewManagerSpec extends ReactViewManager { 11 | public abstract void setCanBeFocused(T wrapper, boolean canBeFocused); 12 | 13 | public abstract void setHasKeyDownPress(T view, boolean value); 14 | 15 | public abstract void setHasKeyUpPress(T view, boolean value); 16 | 17 | public abstract void focus(ExternalKeyboardView view); 18 | 19 | public abstract void setAutoFocus(ExternalKeyboardView view, @Nullable boolean value); 20 | 21 | public abstract void setTintColor(ExternalKeyboardView view, @Nullable Integer value); 22 | 23 | public abstract void setHasOnFocusChanged(ExternalKeyboardView view, boolean value); 24 | 25 | public abstract void setHaloEffect(ExternalKeyboardView view, boolean value); 26 | 27 | public abstract void setGroup(ExternalKeyboardView view, boolean value); 28 | 29 | public abstract void setHaloCornerRadius(ExternalKeyboardView view, float value); 30 | 31 | public abstract void setHaloExpendX(ExternalKeyboardView view, float value); 32 | 33 | public abstract void setHaloExpendY(ExternalKeyboardView view, float value); 34 | 35 | public abstract void setGroupIdentifier(ExternalKeyboardView view, @Nullable String value); 36 | 37 | public abstract void setEnableA11yFocus(ExternalKeyboardView wrapper, boolean enableA11yFocus); 38 | 39 | public abstract void setScreenAutoA11yFocus(ExternalKeyboardView wrapper, boolean enableA11yFocus); 40 | 41 | public abstract void setScreenAutoA11yFocusDelay(ExternalKeyboardView wrapper, int value); 42 | } 43 | -------------------------------------------------------------------------------- /android/src/oldarch/KeyboardFocusGroupManagerSpec.java: -------------------------------------------------------------------------------- 1 | package com.externalkeyboard; 2 | 3 | import android.view.ViewGroup; 4 | 5 | import com.facebook.react.uimanager.ViewGroupManager; 6 | 7 | public abstract class KeyboardFocusGroupManagerSpec extends ViewGroupManager { 8 | public abstract void setTintColor(T wrapper, Integer value); 9 | public abstract void setGroupIdentifier(T wrapper, String groupIdentifier); 10 | } 11 | -------------------------------------------------------------------------------- /android/src/oldarch/TextInputFocusWrapperManagerSpec.java: -------------------------------------------------------------------------------- 1 | package com.externalkeyboard; 2 | 3 | import android.view.ViewGroup; 4 | 5 | import androidx.annotation.Nullable; 6 | 7 | import com.externalkeyboard.views.TextInputFocusWrapper.TextInputFocusWrapper; 8 | import com.facebook.react.uimanager.ViewGroupManager; 9 | 10 | public abstract class TextInputFocusWrapperManagerSpec extends ViewGroupManager { 11 | public abstract void setCanBeFocused(T wrapper, boolean canBeFocused); 12 | 13 | public abstract void setFocusType(T wrapper, int focusType); 14 | 15 | public abstract void setBlurType(T wrapper, int blurType); 16 | 17 | public abstract void setHaloEffect(TextInputFocusWrapper view, boolean value); 18 | 19 | public abstract void setTintColor(TextInputFocusWrapper view, Integer value); 20 | 21 | public abstract void setBlurOnSubmit(TextInputFocusWrapper view, boolean value); 22 | 23 | public abstract void setMultiline(TextInputFocusWrapper view, boolean value); 24 | 25 | public abstract void setGroupIdentifier(TextInputFocusWrapper view, @Nullable String value); 26 | } 27 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['module:@react-native/babel-preset'], 3 | }; 4 | -------------------------------------------------------------------------------- /example/.bundle/config: -------------------------------------------------------------------------------- 1 | BUNDLE_PATH: "vendor/bundle" 2 | BUNDLE_FORCE_RUBY_PLATFORM: 1 3 | -------------------------------------------------------------------------------- /example/.node-version: -------------------------------------------------------------------------------- 1 | 18 2 | -------------------------------------------------------------------------------- /example/.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /example/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | -------------------------------------------------------------------------------- /example/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # You may use http://rbenv.org/ or https://rvm.io/ to install and use this version 4 | ruby ">= 2.6.10" 5 | 6 | # Exclude problematic versions of cocoapods and activesupport that causes build failures. 7 | gem 'cocoapods', '>= 1.13', '!= 1.15.0', '!= 1.15.1' 8 | gem 'activesupport', '>= 6.1.7.5', '!= 7.1.0' 9 | gem 'xcodeproj', '< 1.26.0' 10 | -------------------------------------------------------------------------------- /example/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.6) 5 | rexml 6 | activesupport (6.1.7.6) 7 | concurrent-ruby (~> 1.0, >= 1.0.2) 8 | i18n (>= 1.6, < 2) 9 | minitest (>= 5.1) 10 | tzinfo (~> 2.0) 11 | zeitwerk (~> 2.3) 12 | addressable (2.8.6) 13 | public_suffix (>= 2.0.2, < 6.0) 14 | algoliasearch (1.27.5) 15 | httpclient (~> 2.8, >= 2.8.3) 16 | json (>= 1.5.1) 17 | atomos (0.1.3) 18 | claide (1.1.0) 19 | cocoapods (1.15.2) 20 | addressable (~> 2.8) 21 | claide (>= 1.0.2, < 2.0) 22 | cocoapods-core (= 1.15.2) 23 | cocoapods-deintegrate (>= 1.0.3, < 2.0) 24 | cocoapods-downloader (>= 2.1, < 3.0) 25 | cocoapods-plugins (>= 1.0.0, < 2.0) 26 | cocoapods-search (>= 1.0.0, < 2.0) 27 | cocoapods-trunk (>= 1.6.0, < 2.0) 28 | cocoapods-try (>= 1.1.0, < 2.0) 29 | colored2 (~> 3.1) 30 | escape (~> 0.0.4) 31 | fourflusher (>= 2.3.0, < 3.0) 32 | gh_inspector (~> 1.0) 33 | molinillo (~> 0.8.0) 34 | nap (~> 1.0) 35 | ruby-macho (>= 2.3.0, < 3.0) 36 | xcodeproj (>= 1.23.0, < 2.0) 37 | cocoapods-core (1.15.2) 38 | activesupport (>= 5.0, < 8) 39 | addressable (~> 2.8) 40 | algoliasearch (~> 1.0) 41 | concurrent-ruby (~> 1.1) 42 | fuzzy_match (~> 2.0.4) 43 | nap (~> 1.0) 44 | netrc (~> 0.11) 45 | public_suffix (~> 4.0) 46 | typhoeus (~> 1.0) 47 | cocoapods-deintegrate (1.0.5) 48 | cocoapods-downloader (2.1) 49 | cocoapods-plugins (1.0.0) 50 | nap 51 | cocoapods-search (1.0.1) 52 | cocoapods-trunk (1.6.0) 53 | nap (>= 0.8, < 2.0) 54 | netrc (~> 0.11) 55 | cocoapods-try (1.2.0) 56 | colored2 (3.1.2) 57 | concurrent-ruby (1.2.3) 58 | escape (0.0.4) 59 | ethon (0.16.0) 60 | ffi (>= 1.15.0) 61 | ffi (1.16.3) 62 | fourflusher (2.3.1) 63 | fuzzy_match (2.0.4) 64 | gh_inspector (1.1.3) 65 | httpclient (2.8.3) 66 | i18n (1.14.1) 67 | concurrent-ruby (~> 1.0) 68 | json (2.7.1) 69 | minitest (5.22.2) 70 | molinillo (0.8.0) 71 | nanaimo (0.3.0) 72 | nap (1.1.0) 73 | netrc (0.11.0) 74 | public_suffix (4.0.7) 75 | rexml (3.2.6) 76 | ruby-macho (2.5.1) 77 | typhoeus (1.4.1) 78 | ethon (>= 0.9.0) 79 | tzinfo (2.0.6) 80 | concurrent-ruby (~> 1.0) 81 | xcodeproj (1.24.0) 82 | CFPropertyList (>= 2.3.3, < 4.0) 83 | atomos (~> 0.1.3) 84 | claide (>= 1.0.2, < 2.0) 85 | colored2 (~> 3.1) 86 | nanaimo (~> 0.3.0) 87 | rexml (~> 3.2.4) 88 | zeitwerk (2.6.13) 89 | 90 | PLATFORMS 91 | ruby 92 | 93 | DEPENDENCIES 94 | cocoapods (>= 1.11.3) 95 | 96 | RUBY VERSION 97 | ruby 2.6.10p210 98 | 99 | BUNDLED WITH 100 | 1.17.2 101 | -------------------------------------------------------------------------------- /example/android/app/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArturKalach/react-native-external-keyboard/6d4ae711800c15f095f088fadfb03ec2ceb177f8/example/android/app/debug.keystore -------------------------------------------------------------------------------- /example/android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 13 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/externalkeyboard/example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package externalkeyboard.example 2 | 3 | import com.facebook.react.ReactActivity 4 | import com.facebook.react.ReactActivityDelegate 5 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled 6 | import com.facebook.react.defaults.DefaultReactActivityDelegate 7 | 8 | class MainActivity : ReactActivity() { 9 | 10 | /** 11 | * Returns the name of the main component registered from JavaScript. This is used to schedule 12 | * rendering of the component. 13 | */ 14 | override fun getMainComponentName(): String = "ExternalKeyboardExample" 15 | 16 | /** 17 | * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate] 18 | * which allows you to enable New Architecture with a single boolean flags [fabricEnabled] 19 | */ 20 | override fun createReactActivityDelegate(): ReactActivityDelegate = 21 | DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled) 22 | } 23 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/externalkeyboard/example/MainApplication.kt: -------------------------------------------------------------------------------- 1 | package externalkeyboard.example 2 | 3 | import android.app.Application 4 | import com.facebook.react.PackageList 5 | import com.facebook.react.ReactApplication 6 | import com.facebook.react.ReactHost 7 | import com.facebook.react.ReactNativeHost 8 | import com.facebook.react.ReactPackage 9 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load 10 | import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost 11 | import com.facebook.react.defaults.DefaultReactNativeHost 12 | import com.facebook.react.soloader.OpenSourceMergedSoMapping 13 | import com.facebook.soloader.SoLoader 14 | 15 | class MainApplication : Application(), ReactApplication { 16 | 17 | override val reactNativeHost: ReactNativeHost = 18 | object : DefaultReactNativeHost(this) { 19 | override fun getPackages(): List = 20 | PackageList(this).packages.apply { 21 | // Packages that cannot be autolinked yet can be added manually here, for example: 22 | // add(MyReactNativePackage()) 23 | } 24 | 25 | override fun getJSMainModuleName(): String = "index" 26 | 27 | override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG 28 | 29 | override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED 30 | override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED 31 | } 32 | 33 | override val reactHost: ReactHost 34 | get() = getDefaultReactHost(applicationContext, reactNativeHost) 35 | 36 | override fun onCreate() { 37 | super.onCreate() 38 | SoLoader.init(this, OpenSourceMergedSoMapping) 39 | if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { 40 | // If you opted-in for the New Architecture, we load the native entry point for this app. 41 | load() 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/rn_edit_text_material.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 22 | 23 | 24 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArturKalach/react-native-external-keyboard/6d4ae711800c15f095f088fadfb03ec2ceb177f8/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArturKalach/react-native-external-keyboard/6d4ae711800c15f095f088fadfb03ec2ceb177f8/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArturKalach/react-native-external-keyboard/6d4ae711800c15f095f088fadfb03ec2ceb177f8/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArturKalach/react-native-external-keyboard/6d4ae711800c15f095f088fadfb03ec2ceb177f8/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArturKalach/react-native-external-keyboard/6d4ae711800c15f095f088fadfb03ec2ceb177f8/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArturKalach/react-native-external-keyboard/6d4ae711800c15f095f088fadfb03ec2ceb177f8/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArturKalach/react-native-external-keyboard/6d4ae711800c15f095f088fadfb03ec2ceb177f8/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArturKalach/react-native-external-keyboard/6d4ae711800c15f095f088fadfb03ec2ceb177f8/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArturKalach/react-native-external-keyboard/6d4ae711800c15f095f088fadfb03ec2ceb177f8/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArturKalach/react-native-external-keyboard/6d4ae711800c15f095f088fadfb03ec2ceb177f8/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | ExternalKeyboardExample 3 | 4 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | buildToolsVersion = "35.0.0" 4 | minSdkVersion = 24 5 | compileSdkVersion = 35 6 | targetSdkVersion = 34 7 | ndkVersion = "26.1.10909125" 8 | kotlinVersion = "1.9.24" 9 | } 10 | repositories { 11 | google() 12 | mavenCentral() 13 | } 14 | dependencies { 15 | classpath("com.android.tools.build:gradle") 16 | classpath("com.facebook.react:react-native-gradle-plugin") 17 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin") 18 | } 19 | } 20 | 21 | apply plugin: "com.facebook.react.rootproject" 22 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx512m -XX:MaxMetaspaceSize=256m 13 | org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | # AndroidX package structure to make it clearer which packages are bundled with the 21 | # Android operating system, and which are packaged with your app's APK 22 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 23 | android.useAndroidX=true 24 | 25 | # Use this property to specify which architecture you want to build. 26 | # You can also override it from the CLI using 27 | # ./gradlew -PreactNativeArchitectures=x86_64 28 | reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 29 | 30 | # Use this property to enable support to the new architecture. 31 | # This will allow you to use TurboModules and the Fabric render in 32 | # your application. You should enable this flag either if you want 33 | # to write custom TurboModules/Fabric components OR use libraries that 34 | # are providing them. 35 | newArchEnabled=true 36 | 37 | # Use this property to enable or disable the Hermes JS engine. 38 | # If set to false, you will be using JSC instead. 39 | hermesEnabled=true 40 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArturKalach/react-native-external-keyboard/6d4ae711800c15f095f088fadfb03ec2ceb177f8/example/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /example/android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { includeBuild("../node_modules/@react-native/gradle-plugin") } 2 | plugins { id("com.facebook.react.settings") } 3 | extensions.configure(com.facebook.react.ReactSettingsExtension){ ex -> ex.autolinkLibrariesFromCommand() } 4 | rootProject.name = 'externalkeyboard.example' 5 | include ':app' 6 | includeBuild('../node_modules/@react-native/gradle-plugin') 7 | -------------------------------------------------------------------------------- /example/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ExternalKeyboardExample", 3 | "displayName": "ExternalKeyboardExample" 4 | } -------------------------------------------------------------------------------- /example/babel.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { getConfig } = require('react-native-builder-bob/babel-config'); 3 | const pkg = require('../package.json'); 4 | 5 | const root = path.resolve(__dirname, '..'); 6 | 7 | module.exports = getConfig( 8 | { 9 | presets: ['module:@react-native/babel-preset'], 10 | }, 11 | { root, pkg } 12 | ); 13 | -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | import { AppRegistry } from 'react-native'; 2 | import { App } from './src/App'; 3 | import { name as appName } from './app.json'; 4 | 5 | AppRegistry.registerComponent(appName, () => App); 6 | -------------------------------------------------------------------------------- /example/ios/.xcode.env: -------------------------------------------------------------------------------- 1 | # This `.xcode.env` file is versioned and is used to source the environment 2 | # used when running script phases inside Xcode. 3 | # To customize your local environment, you can create an `.xcode.env.local` 4 | # file that is not versioned. 5 | 6 | # NODE_BINARY variable contains the PATH to the node executable. 7 | # 8 | # Customize the NODE_BINARY variable here. 9 | # For example, to use nvm with brew, add the following line 10 | # . "$(brew --prefix nvm)/nvm.sh" --no-use 11 | export NODE_BINARY=$(command -v node) 12 | -------------------------------------------------------------------------------- /example/ios/ExternalKeyboardExample-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | -------------------------------------------------------------------------------- /example/ios/ExternalKeyboardExample.xcodeproj/xcshareddata/xcschemes/ExternalKeyboardExample.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 55 | 61 | 62 | 63 | 64 | 70 | 72 | 78 | 79 | 80 | 81 | 83 | 84 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /example/ios/ExternalKeyboardExample.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/ExternalKeyboardExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/ExternalKeyboardExample/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : RCTAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /example/ios/ExternalKeyboardExample/AppDelegate.mm: -------------------------------------------------------------------------------- 1 | #import "AppDelegate.h" 2 | 3 | #import 4 | 5 | @implementation AppDelegate 6 | 7 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 8 | { 9 | self.moduleName = @"ExternalKeyboardExample"; 10 | // You can add your custom initial props in the dictionary below. 11 | // They will be passed down to the ViewController used by React Native. 12 | self.initialProps = @{}; 13 | 14 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 15 | } 16 | 17 | - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge 18 | { 19 | return [self bundleURL]; 20 | } 21 | 22 | - (NSURL *)bundleURL 23 | { 24 | #if DEBUG 25 | return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"]; 26 | #else 27 | return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; 28 | #endif 29 | } 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /example/ios/ExternalKeyboardExample/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ios-marketing", 45 | "scale" : "1x", 46 | "size" : "1024x1024" 47 | } 48 | ], 49 | "info" : { 50 | "author" : "xcode", 51 | "version" : 1 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /example/ios/ExternalKeyboardExample/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /example/ios/ExternalKeyboardExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ExternalKeyboardExample 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(CURRENT_PROJECT_VERSION) 25 | LSRequiresIPhoneOS 26 | 27 | NSAppTransportSecurity 28 | 29 | 30 | NSAllowsArbitraryLoads 31 | 32 | NSAllowsLocalNetworking 33 | 34 | 35 | NSLocationWhenInUseUsageDescription 36 | 37 | UILaunchStoryboardName 38 | LaunchScreen 39 | UIRequiredDeviceCapabilities 40 | 41 | arm64 42 | 43 | UISupportedInterfaceOrientations 44 | 45 | UIInterfaceOrientationPortrait 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | UIViewControllerBasedStatusBarAppearance 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /example/ios/ExternalKeyboardExample/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 24 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /example/ios/ExternalKeyboardExample/PrivacyInfo.xcprivacy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrivacyAccessedAPITypes 6 | 7 | 8 | NSPrivacyAccessedAPIType 9 | NSPrivacyAccessedAPICategoryFileTimestamp 10 | NSPrivacyAccessedAPITypeReasons 11 | 12 | C617.1 13 | 14 | 15 | 16 | NSPrivacyAccessedAPIType 17 | NSPrivacyAccessedAPICategoryUserDefaults 18 | NSPrivacyAccessedAPITypeReasons 19 | 20 | CA92.1 21 | 22 | 23 | 24 | NSPrivacyAccessedAPIType 25 | NSPrivacyAccessedAPICategorySystemBootTime 26 | NSPrivacyAccessedAPITypeReasons 27 | 28 | 35F9.1 29 | 30 | 31 | 32 | NSPrivacyCollectedDataTypes 33 | 34 | NSPrivacyTracking 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/ExternalKeyboardExample/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | @autoreleasepool { 8 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /example/ios/ExternalKeyboardExampleTests/ExternalKeyboardExampleTests.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | #import 5 | #import 6 | 7 | #define TIMEOUT_SECONDS 600 8 | #define TEXT_TO_LOOK_FOR @"Welcome to React" 9 | 10 | @interface ExternalKeyboardExampleTests : XCTestCase 11 | 12 | @end 13 | 14 | @implementation ExternalKeyboardExampleTests 15 | 16 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL (^)(UIView *view))test 17 | { 18 | if (test(view)) { 19 | return YES; 20 | } 21 | for (UIView *subview in [view subviews]) { 22 | if ([self findSubviewInView:subview matching:test]) { 23 | return YES; 24 | } 25 | } 26 | return NO; 27 | } 28 | 29 | - (void)testRendersWelcomeScreen 30 | { 31 | UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController]; 32 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; 33 | BOOL foundElement = NO; 34 | 35 | __block NSString *redboxError = nil; 36 | #ifdef DEBUG 37 | RCTSetLogFunction( 38 | ^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { 39 | if (level >= RCTLogLevelError) { 40 | redboxError = message; 41 | } 42 | }); 43 | #endif 44 | 45 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { 46 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 47 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 48 | 49 | foundElement = [self findSubviewInView:vc.view 50 | matching:^BOOL(UIView *view) { 51 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { 52 | return YES; 53 | } 54 | return NO; 55 | }]; 56 | } 57 | 58 | #ifdef DEBUG 59 | RCTSetLogFunction(RCTDefaultLogFunction); 60 | #endif 61 | 62 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); 63 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); 64 | } 65 | 66 | @end 67 | -------------------------------------------------------------------------------- /example/ios/ExternalKeyboardExampleTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /example/ios/File.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // ExternalKeyboardExample 4 | // 5 | 6 | import Foundation 7 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | ENV['RCT_NEW_ARCH_ENABLED'] = '1' 2 | 3 | # Resolve react_native_pods.rb with node to allow for hoisting 4 | require Pod::Executable.execute_command('node', ['-p', 5 | 'require.resolve( 6 | "react-native/scripts/react_native_pods.rb", 7 | {paths: [process.argv[1]]}, 8 | )', __dir__]).strip 9 | 10 | platform :ios, min_ios_version_supported 11 | prepare_react_native_project! 12 | 13 | linkage = ENV['USE_FRAMEWORKS'] 14 | if linkage != nil 15 | Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green 16 | use_frameworks! :linkage => linkage.to_sym 17 | end 18 | 19 | target 'ExternalKeyboardExample' do 20 | config = use_native_modules! 21 | 22 | use_react_native!( 23 | :path => config[:reactNativePath], 24 | # An absolute path to your application root. 25 | :app_path => "#{Pod::Config.instance.installation_root}/.." 26 | ) 27 | 28 | target 'ExternalKeyboardExampleTests' do 29 | inherit! :complete 30 | # Pods for testing 31 | end 32 | 33 | 34 | pre_install do |installer| 35 | system("cd ../../ && npx bob build") 36 | end 37 | 38 | post_install do |installer| 39 | # https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202 40 | react_native_post_install( 41 | installer, 42 | config[:reactNativePath], 43 | :mac_catalyst_enabled => false, 44 | # :ccache_enabled => true 45 | ) 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /example/metro.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { getDefaultConfig } = require('@react-native/metro-config'); 3 | const { getConfig } = require('react-native-builder-bob/metro-config'); 4 | const pkg = require('../package.json'); 5 | 6 | const root = path.resolve(__dirname, '..'); 7 | 8 | /** 9 | * Metro configuration 10 | * https://facebook.github.io/metro/docs/configuration 11 | * 12 | * @type {import('metro-config').MetroConfig} 13 | */ 14 | module.exports = getConfig(getDefaultConfig(__dirname), { 15 | root, 16 | pkg, 17 | project: __dirname, 18 | }); 19 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "external-keyboard-example", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "android": "react-native run-android", 7 | "ios": "react-native run-ios", 8 | "start": "react-native start", 9 | "pods": "pod-install --quiet" 10 | }, 11 | "dependencies": { 12 | "@react-navigation/bottom-tabs": "^7.2.0", 13 | "@react-navigation/native": "^7.0.14", 14 | "@react-navigation/native-stack": "^7.2.0", 15 | "react": "18.3.1", 16 | "react-native": "0.76.5", 17 | "react-native-gesture-handler": "^2.22.1", 18 | "react-native-safe-area-context": "^5.1.0", 19 | "react-native-screens": "^4.5.0" 20 | }, 21 | "devDependencies": { 22 | "@babel/core": "^7.25.2", 23 | "@babel/preset-env": "^7.25.3", 24 | "@babel/runtime": "^7.25.0", 25 | "@react-native-community/cli": "15.0.1", 26 | "@react-native-community/cli-platform-android": "15.0.1", 27 | "@react-native-community/cli-platform-ios": "15.0.1", 28 | "@react-native/babel-preset": "0.76.5", 29 | "@react-native/metro-config": "0.76.5", 30 | "@react-native/typescript-config": "0.76.5", 31 | "react-native-builder-bob": "^0.35.2" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /example/react-native.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const pkg = require('../package.json'); 3 | 4 | module.exports = { 5 | project: { 6 | ios: { 7 | automaticPodsInstallation: true, 8 | }, 9 | }, 10 | dependencies: { 11 | [pkg.name]: { 12 | root: path.join(__dirname, '..'), 13 | platforms: { 14 | // Codegen script incorrectly fails without this 15 | // So we explicitly specify the platforms with empty object 16 | ios: {}, 17 | android: {}, 18 | }, 19 | }, 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /example/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { GestureHandlerRootView } from 'react-native-gesture-handler'; 2 | 3 | import { Home } from './screens/Home/Home'; 4 | import { Button, SafeAreaView, StyleSheet } from 'react-native'; 5 | import { View, Text } from 'react-native'; 6 | import { createNativeStackNavigator } from '@react-navigation/native-stack'; 7 | import { 8 | NavigationContainer, 9 | type NavigationProp, 10 | } from '@react-navigation/native'; 11 | 12 | export function DetailsScreen() { 13 | return ( 14 | 15 | 16 | 17 | 18 | 19 | ); 20 | } 21 | 22 | const Stack = createNativeStackNavigator(); 23 | 24 | function HomeScreen({ navigation }: { navigation: NavigationProp }) { 25 | return ( 26 | 27 | Home Screen 28 |