From 1651da45c61b8598c90e7a23557aff7b4df41c82 Mon Sep 17 00:00:00 2001
From: Kirill Zyusko <zyusko.kirik@gmail.com>
Date: Sat, 23 Dec 2023 15:28:13 +0100
Subject: [PATCH] feat: dismiss keyboard (#306)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

## 📜 Description

Added `KeyboardController.dismiss` method.

## 💡 Motivation and Context

Actually there is quite a lot of motivation behind such functionality.
Let's go one-by one

### 1️⃣ Community request

In Algolia I constantly see that people are looking for `dismiss`
method.

|One week ago|Two weeks ago|One month ago|
|--------------|---------------|----------------|
|<img width="580" alt="image"
src="https://github.com/kirillzyusko/react-native-keyboard-controller/assets/22820318/7a9de97e-cdcb-48a8-91f7-cb818338aaf5">|<img
width="577" alt="image"
src="https://github.com/kirillzyusko/react-native-keyboard-controller/assets/22820318/55f9760d-9a5b-43cc-b274-fb189dab60c9">|<img
width="580" alt="image"
src="https://github.com/kirillzyusko/react-native-keyboard-controller/assets/22820318/406fd6e8-91b6-494a-8a86-f9b7dc3d0655">|

### 2️⃣ `react-native` flaws implementation

`react-native` implementation is based on the next code:

```ts
class Keyboard {
  // ...
  dismiss(): void {
    dismissKeyboard();
  }
  // ...
}

function dismissKeyboard() {
  TextInputState.blurTextInput(TextInputState.currentlyFocusedInput());
}
```

Where `currentlyFocusedInput` is set in:

```ts
function focusInput(textField: ?ComponentRef): void {
  if (currentlyFocusedInputRef !== textField && textField != null) {
    currentlyFocusedInputRef = textField;
  }
}
```

And the usage of this function:

```ts
const _onFocus = (event: FocusEvent) => {
  TextInputState.focusInput(inputRef.current);
  if (props.onFocus) {
    props.onFocus(event);
  }
};
```

So theoretically if you use `TextInput` component that is not based on
the implementation from `react-native` core (i. e. you decided to write
your own component), then `Keyboard.dismiss` most likely will not work 😓

### 3️⃣ Standalone module

I'm going to continue the development of this library and in the future
I may need to rely on the presence/implementation of my own methods.

For example I'm thinking about `Toolbar` component implementation (the
component above the keyboard with prev/next/done buttons). In my opinion
it'll be strange when this component will fully depend on the methods
from this package **AND** on a single function from `react-native`
`Keyboard` module 🤔

So I think it's better to have own equivalent.

## 📢 Changelog

<!-- High level overview of important changes -->
<!-- For example: fixed status bar manipulation; added new types
declarations; -->
<!-- If your changes don't affect one of platform/language below - then
remove this platform/language -->

### Docs
- re-write docs about existing `setInputMode`/`setDefaultMode`;
- added documentation about `dismiss` method;

### JS
- added `dismiss` in `specs`, `types`;
- added `mock` and unit-test;

### iOS
- used with `resignFirstResponder` selector to dismiss a keyboard
([source](https://stackoverflow.com/a/11768282/9272042));
- added `dismiss` method to `KeyboardController`;

### Android
- used `hideSoftInputFromWindow` to close a keyboard
([source](https://stackoverflow.com/a/1109108/9272042));
- added `dismiss` method to `KeyboardController`;

## 🤔 How Has This Been Tested?

Tested manually on:
- Pixel 3a (Android 13, emulator);
- iPhone 15 Pro (iOS 17.2 simulator);

## 📸 Screenshots (if appropriate):

|Android|iOS|
|--------|----|
|<video
src="https://github.com/kirillzyusko/react-native-keyboard-controller/assets/22820318/39f47e5e-b15f-42bf-8af3-d1d7b4a76276">|<video
src="https://github.com/kirillzyusko/react-native-keyboard-controller/assets/22820318/fbd1324d-d4de-44fe-a468-54b1f1d77a7e">|

## 📝 Checklist

- [x] CI successfully passed
---
 .../__tests__/close-keyboard.spec.tsx         | 25 ++++++++++
 FabricExample/src/constants/screenNames.ts    |  1 +
 .../src/navigation/ExamplesStack/index.tsx    | 10 ++++
 .../src/screens/Examples/Close/index.tsx      | 30 ++++++++++++
 .../src/screens/Examples/Main/constants.ts    |  6 +++
 .../KeyboardControllerModule.kt               |  4 ++
 .../modules/KeyboardControllerModuleImpl.kt   | 13 +++++
 .../KeyboardControllerModule.kt               |  5 ++
 docs/docs/api/keyboard-controller.md          | 48 +++++++++++--------
 example/__tests__/close-keyboard.spec.tsx     | 25 ++++++++++
 example/src/constants/screenNames.ts          |  1 +
 .../src/navigation/ExamplesStack/index.tsx    | 10 ++++
 example/src/screens/Examples/Close/index.tsx  | 30 ++++++++++++
 .../src/screens/Examples/Main/constants.ts    |  6 +++
 ios/KeyboardControllerModule.mm               | 14 ++++++
 jest/index.js                                 |  1 +
 package.json                                  |  1 +
 src/bindings.ts                               |  1 +
 src/specs/NativeKeyboardController.ts         |  1 +
 src/types.ts                                  |  2 +
 20 files changed, 215 insertions(+), 19 deletions(-)
 create mode 100644 FabricExample/__tests__/close-keyboard.spec.tsx
 create mode 100644 FabricExample/src/screens/Examples/Close/index.tsx
 create mode 100644 example/__tests__/close-keyboard.spec.tsx
 create mode 100644 example/src/screens/Examples/Close/index.tsx

diff --git a/FabricExample/__tests__/close-keyboard.spec.tsx b/FabricExample/__tests__/close-keyboard.spec.tsx
new file mode 100644
index 0000000000..0ad47bb855
--- /dev/null
+++ b/FabricExample/__tests__/close-keyboard.spec.tsx
@@ -0,0 +1,25 @@
+import React from 'react';
+import { Button } from 'react-native';
+import { fireEvent, render } from '@testing-library/react-native';
+
+import { KeyboardController } from 'react-native-keyboard-controller';
+
+function CloseKeyboard() {
+  return (
+    <Button
+      title='Close keyboard'
+      testID='close_keyboard'
+      onPress={() => KeyboardController.dismiss()}
+    />
+  );
+}
+
+describe('closing keyboard flow', () => {
+  it('should have a mock version of `KeyboardController.dismiss`', () => {
+    const { getByTestId } = render(<CloseKeyboard />);
+
+    fireEvent.press(getByTestId('close_keyboard'));
+
+    expect(KeyboardController.dismiss).toBeCalledTimes(1);
+  });
+});
diff --git a/FabricExample/src/constants/screenNames.ts b/FabricExample/src/constants/screenNames.ts
index 911d71bbac..68b1e233e4 100644
--- a/FabricExample/src/constants/screenNames.ts
+++ b/FabricExample/src/constants/screenNames.ts
@@ -14,4 +14,5 @@ export enum ScreenNames {
   NATIVE_STACK = 'NATIVE_STACK',
   KEYBOARD_AVOIDING_VIEW = 'KEYBOARD_AVOIDING_VIEW',
   ENABLED_DISABLED = 'ENABLED_DISABLED',
+  CLOSE = 'CLOSE',
 }
diff --git a/FabricExample/src/navigation/ExamplesStack/index.tsx b/FabricExample/src/navigation/ExamplesStack/index.tsx
index 61d8f62bc6..a26340cf4e 100644
--- a/FabricExample/src/navigation/ExamplesStack/index.tsx
+++ b/FabricExample/src/navigation/ExamplesStack/index.tsx
@@ -16,6 +16,7 @@ import NativeStack from '../NestedStack';
 import KeyboardAvoidingViewExample from '../../screens/Examples/KeyboardAvoidingView';
 import EnabledDisabled from '../../screens/Examples/EnabledDisabled';
 import AwareScrollViewStickyFooter from '../../screens/Examples/AwareScrollViewStickyFooter';
+import CloseScreen from '../../screens/Examples/Close';
 
 export type ExamplesStackParamList = {
   [ScreenNames.ANIMATED_EXAMPLE]: undefined;
@@ -31,6 +32,7 @@ export type ExamplesStackParamList = {
   [ScreenNames.NATIVE_STACK]: undefined;
   [ScreenNames.KEYBOARD_AVOIDING_VIEW]: undefined;
   [ScreenNames.ENABLED_DISABLED]: undefined;
+  [ScreenNames.CLOSE]: undefined;
 };
 
 const Stack = createStackNavigator<ExamplesStackParamList>();
@@ -76,6 +78,9 @@ const options = {
   [ScreenNames.ENABLED_DISABLED]: {
     title: 'Enabled/disabled',
   },
+  [ScreenNames.CLOSE]: {
+    title: 'Close keyboard',
+  },
 };
 
 const ExamplesStack = () => (
@@ -145,6 +150,11 @@ const ExamplesStack = () => (
       component={EnabledDisabled}
       options={options[ScreenNames.ENABLED_DISABLED]}
     />
+    <Stack.Screen
+      name={ScreenNames.CLOSE}
+      component={CloseScreen}
+      options={options[ScreenNames.CLOSE]}
+    />
   </Stack.Navigator>
 );
 
diff --git a/FabricExample/src/screens/Examples/Close/index.tsx b/FabricExample/src/screens/Examples/Close/index.tsx
new file mode 100644
index 0000000000..1d8150986f
--- /dev/null
+++ b/FabricExample/src/screens/Examples/Close/index.tsx
@@ -0,0 +1,30 @@
+import { Button, StyleSheet, TextInput, View } from "react-native";
+import { KeyboardController } from "react-native-keyboard-controller";
+
+function CloseScreen() {
+  return (
+    <View>
+      <Button
+        title="Close keyboard"
+        onPress={() => KeyboardController.dismiss()}
+        testID="close_keyboard_button"
+      />
+      <TextInput style={styles.input} placeholder="Touch to open the keyboard..." placeholderTextColor="#7C7C7C" />
+    </View>
+  );
+};
+
+const styles = StyleSheet.create({
+  input: {
+    height: 50,
+    width: "84%",
+    borderWidth: 2,
+    borderColor: "#3C3C3C",
+    borderRadius: 8,
+    alignSelf: 'center',
+    paddingHorizontal: 8,
+    marginTop: 16,
+  },
+});
+
+export default CloseScreen;
\ No newline at end of file
diff --git a/FabricExample/src/screens/Examples/Main/constants.ts b/FabricExample/src/screens/Examples/Main/constants.ts
index e3dc5bdeae..2dc10816bc 100644
--- a/FabricExample/src/screens/Examples/Main/constants.ts
+++ b/FabricExample/src/screens/Examples/Main/constants.ts
@@ -80,4 +80,10 @@ export const examples: Example[] = [
     info: ScreenNames.ENABLED_DISABLED,
     icons: '💡',
   },
+  {
+    title: 'Close',
+    testID: 'close',
+    info: ScreenNames.CLOSE,
+    icons: '❌',
+  },
 ];
\ No newline at end of file
diff --git a/android/src/fabric/java/com/reactnativekeyboardcontroller/KeyboardControllerModule.kt b/android/src/fabric/java/com/reactnativekeyboardcontroller/KeyboardControllerModule.kt
index 4423ac70db..8d9c0f3bce 100644
--- a/android/src/fabric/java/com/reactnativekeyboardcontroller/KeyboardControllerModule.kt
+++ b/android/src/fabric/java/com/reactnativekeyboardcontroller/KeyboardControllerModule.kt
@@ -16,6 +16,10 @@ class KeyboardControllerModule(mReactContext: ReactApplicationContext) : NativeK
     module.setDefaultMode()
   }
 
+  override fun dismiss() {
+    module.dismiss()
+  }
+
   override fun addListener(eventName: String?) {
     /* Required for RN built-in Event Emitter Calls. */
   }
diff --git a/android/src/main/java/com/reactnativekeyboardcontroller/modules/KeyboardControllerModuleImpl.kt b/android/src/main/java/com/reactnativekeyboardcontroller/modules/KeyboardControllerModuleImpl.kt
index ae2596009c..642d8c9198 100644
--- a/android/src/main/java/com/reactnativekeyboardcontroller/modules/KeyboardControllerModuleImpl.kt
+++ b/android/src/main/java/com/reactnativekeyboardcontroller/modules/KeyboardControllerModuleImpl.kt
@@ -1,6 +1,9 @@
 package com.reactnativekeyboardcontroller.modules
 
+import android.content.Context
+import android.view.View
 import android.view.WindowManager
+import android.view.inputmethod.InputMethodManager
 import com.facebook.react.bridge.ReactApplicationContext
 import com.facebook.react.bridge.UiThreadUtil
 
@@ -15,6 +18,16 @@ class KeyboardControllerModuleImpl(private val mReactContext: ReactApplicationCo
     setSoftInputMode(mDefaultMode)
   }
 
+  fun dismiss() {
+    val activity = mReactContext.currentActivity
+    val view: View? = activity?.currentFocus
+
+    if (view != null) {
+      val imm = activity.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
+      imm?.hideSoftInputFromWindow(view.windowToken, 0)
+    }
+  }
+
   private fun setSoftInputMode(mode: Int) {
     UiThreadUtil.runOnUiThread {
       if (getCurrentMode() != mode) {
diff --git a/android/src/paper/java/com/reactnativekeyboardcontroller/KeyboardControllerModule.kt b/android/src/paper/java/com/reactnativekeyboardcontroller/KeyboardControllerModule.kt
index 6972b20770..54728a3996 100644
--- a/android/src/paper/java/com/reactnativekeyboardcontroller/KeyboardControllerModule.kt
+++ b/android/src/paper/java/com/reactnativekeyboardcontroller/KeyboardControllerModule.kt
@@ -20,6 +20,11 @@ class KeyboardControllerModule(mReactContext: ReactApplicationContext) : ReactCo
     module.setDefaultMode()
   }
 
+  @ReactMethod
+  fun dismiss() {
+    module.dismiss()
+  }
+
   @Suppress("detekt:UnusedParameter")
   @ReactMethod
   fun addListener(eventName: String?) {
diff --git a/docs/docs/api/keyboard-controller.md b/docs/docs/api/keyboard-controller.md
index 2056685fe3..56f7253e5d 100644
--- a/docs/docs/api/keyboard-controller.md
+++ b/docs/docs/api/keyboard-controller.md
@@ -1,30 +1,40 @@
 ---
 sidebar_position: 5
-keywords: [react-native-keyboard-controller, KeyboardController, module, windowSoftInputMode, adjustResize, adjustPan]
+keywords: [react-native-keyboard-controller, react-native, KeyboardController, module, dismiss, dismiss keyboard, windowSoftInputMode, adjustResize, adjustPan]
 ---
 
 # KeyboardController
 
-`KeyboardController` is an object which has two functions:
+The `KeyboardController` module in React Native provides a convenient set of methods for managing the behavior of the keyboard. With seamless runtime adjustments, this module allows developers to dynamically change the `windowSoftInputMode` on Android and dismiss the keyboard on both platforms.
 
-- `setInputMode` - used to change `windowSoftInputMode` in runtime;
-- `setDefaultMode` - used to restore default `windowSoftInputMode` (which is declared in `AndroidManifest.xml`);
+## Methods
 
-## Example
+### `setInputMode`
+
+This method is used to dynamically change the `windowSoftInputMode` during runtime in an Android application. It takes an argument that specifies the desired input mode. The example provided sets the input mode to `SOFT_INPUT_ADJUST_RESIZE`:
 
 ```ts
-import {
-  KeyboardController,
-  AndroidSoftInputModes,
-} from "react-native-keyboard-controller";
-
-export const useResizeMode = () => {
-  useEffect(() => {
-    KeyboardController.setInputMode(
-      AndroidSoftInputModes.SOFT_INPUT_ADJUST_RESIZE
-    );
-
-    return () => KeyboardController.setDefaultMode();
-  }, []);
-};
+KeyboardController.setInputMode(AndroidSoftInputModes.SOFT_INPUT_ADJUST_RESIZE);
 ```
+
+### `setDefaultMode`
+
+This method is used to restore the default `windowSoftInputMode` declared in the `AndroidManifest.xml`. It resets the input mode to the default value:
+
+```ts
+KeyboardController.setDefaultMode();
+```
+
+### `dismiss`
+
+This method is used to hide the keyboard. It triggers the dismissal of the keyboard:
+
+```ts
+KeyboardController.dismiss();
+```
+
+:::info What is the difference comparing to `react-native` implementation?
+The equivalent method from `react-native` relies on specific internal components, such as `TextInput`, and may not work as intended if a custom input component is used.
+
+In contrast, the described method enables keyboard dismissal for any focused input, extending functionality beyond the limitations of the default implementation.
+:::
diff --git a/example/__tests__/close-keyboard.spec.tsx b/example/__tests__/close-keyboard.spec.tsx
new file mode 100644
index 0000000000..0ad47bb855
--- /dev/null
+++ b/example/__tests__/close-keyboard.spec.tsx
@@ -0,0 +1,25 @@
+import React from 'react';
+import { Button } from 'react-native';
+import { fireEvent, render } from '@testing-library/react-native';
+
+import { KeyboardController } from 'react-native-keyboard-controller';
+
+function CloseKeyboard() {
+  return (
+    <Button
+      title='Close keyboard'
+      testID='close_keyboard'
+      onPress={() => KeyboardController.dismiss()}
+    />
+  );
+}
+
+describe('closing keyboard flow', () => {
+  it('should have a mock version of `KeyboardController.dismiss`', () => {
+    const { getByTestId } = render(<CloseKeyboard />);
+
+    fireEvent.press(getByTestId('close_keyboard'));
+
+    expect(KeyboardController.dismiss).toBeCalledTimes(1);
+  });
+});
diff --git a/example/src/constants/screenNames.ts b/example/src/constants/screenNames.ts
index 3165c2d9e2..6ea05b530c 100644
--- a/example/src/constants/screenNames.ts
+++ b/example/src/constants/screenNames.ts
@@ -15,4 +15,5 @@ export enum ScreenNames {
   NATIVE_STACK = 'NATIVE_STACK',
   KEYBOARD_AVOIDING_VIEW = 'KEYBOARD_AVOIDING_VIEW',
   ENABLED_DISABLED = 'ENABLED_DISABLED',
+  CLOSE = 'CLOSE',
 }
diff --git a/example/src/navigation/ExamplesStack/index.tsx b/example/src/navigation/ExamplesStack/index.tsx
index 95d2e4566b..09fecc7f6c 100644
--- a/example/src/navigation/ExamplesStack/index.tsx
+++ b/example/src/navigation/ExamplesStack/index.tsx
@@ -17,6 +17,7 @@ import KeyboardAvoidingViewExample from '../../screens/Examples/KeyboardAvoiding
 import ReanimatedChatFlatlist from '../../screens/Examples/ReanimatedChatFlatlist';
 import EnabledDisabled from '../../screens/Examples/EnabledDisabled';
 import AwareScrollViewStickyFooter from '../../screens/Examples/AwareScrollViewStickyFooter';
+import CloseScreen from '../../screens/Examples/Close';
 
 export type ExamplesStackParamList = {
   [ScreenNames.ANIMATED_EXAMPLE]: undefined;
@@ -32,6 +33,7 @@ export type ExamplesStackParamList = {
   [ScreenNames.NATIVE_STACK]: undefined;
   [ScreenNames.KEYBOARD_AVOIDING_VIEW]: undefined;
   [ScreenNames.ENABLED_DISABLED]: undefined;
+  [ScreenNames.CLOSE]: undefined;
 };
 
 const Stack = createStackNavigator<ExamplesStackParamList>();
@@ -80,6 +82,9 @@ const options = {
   [ScreenNames.ENABLED_DISABLED]: {
     title: 'Enabled/disabled',
   },
+  [ScreenNames.CLOSE]: {
+    title: 'Close keyboard',
+  },
 };
 
 const ExamplesStack = () => (
@@ -154,6 +159,11 @@ const ExamplesStack = () => (
       component={EnabledDisabled}
       options={options[ScreenNames.ENABLED_DISABLED]}
     />
+    <Stack.Screen
+      name={ScreenNames.CLOSE}
+      component={CloseScreen}
+      options={options[ScreenNames.CLOSE]}
+    />
   </Stack.Navigator>
 );
 
diff --git a/example/src/screens/Examples/Close/index.tsx b/example/src/screens/Examples/Close/index.tsx
new file mode 100644
index 0000000000..1d8150986f
--- /dev/null
+++ b/example/src/screens/Examples/Close/index.tsx
@@ -0,0 +1,30 @@
+import { Button, StyleSheet, TextInput, View } from "react-native";
+import { KeyboardController } from "react-native-keyboard-controller";
+
+function CloseScreen() {
+  return (
+    <View>
+      <Button
+        title="Close keyboard"
+        onPress={() => KeyboardController.dismiss()}
+        testID="close_keyboard_button"
+      />
+      <TextInput style={styles.input} placeholder="Touch to open the keyboard..." placeholderTextColor="#7C7C7C" />
+    </View>
+  );
+};
+
+const styles = StyleSheet.create({
+  input: {
+    height: 50,
+    width: "84%",
+    borderWidth: 2,
+    borderColor: "#3C3C3C",
+    borderRadius: 8,
+    alignSelf: 'center',
+    paddingHorizontal: 8,
+    marginTop: 16,
+  },
+});
+
+export default CloseScreen;
\ No newline at end of file
diff --git a/example/src/screens/Examples/Main/constants.ts b/example/src/screens/Examples/Main/constants.ts
index ce99a82e4d..9e142fc187 100644
--- a/example/src/screens/Examples/Main/constants.ts
+++ b/example/src/screens/Examples/Main/constants.ts
@@ -86,4 +86,10 @@ export const examples: Example[] = [
     info: ScreenNames.ENABLED_DISABLED,
     icons: '💡',
   },
+  {
+    title: 'Close',
+    testID: 'close',
+    info: ScreenNames.CLOSE,
+    icons: '❌',
+  },
 ];
diff --git a/ios/KeyboardControllerModule.mm b/ios/KeyboardControllerModule.mm
index 514ad88dbe..f5eeb9a779 100644
--- a/ios/KeyboardControllerModule.mm
+++ b/ios/KeyboardControllerModule.mm
@@ -61,6 +61,20 @@ - (void)setInputMode:(double)mode
 {
 }
 
+#ifdef RCT_NEW_ARCH_ENABLED
+- (void)dismiss
+#else
+RCT_EXPORT_METHOD(dismiss)
+#endif
+{
+  dispatch_async(dispatch_get_main_queue(), ^{
+    [[UIApplication sharedApplication] sendAction:@selector(resignFirstResponder)
+                                               to:nil
+                                             from:nil
+                                         forEvent:nil];
+  });
+}
+
 + (KeyboardController *)shared
 {
   return shared;
diff --git a/jest/index.js b/jest/index.js
index 1f01c759d0..b4af6b1d27 100644
--- a/jest/index.js
+++ b/jest/index.js
@@ -39,6 +39,7 @@ const mock = {
   KeyboardController: {
     setInputMode: jest.fn(),
     setDefaultMode: jest.fn(),
+    dismiss: jest.fn(),
   },
   KeyboardEvents: {
     addListener: jest.fn(() => ({ remove: jest.fn() })),
diff --git a/package.json b/package.json
index 1310e733a5..4d2c7022a6 100644
--- a/package.json
+++ b/package.json
@@ -41,6 +41,7 @@
     "react-native",
     "keyboard",
     "animation",
+    "dismiss",
     "focused input",
     "text changed",
     "avoiding view",
diff --git a/src/bindings.ts b/src/bindings.ts
index 0ec4fd6dea..ca3890ffc1 100644
--- a/src/bindings.ts
+++ b/src/bindings.ts
@@ -12,6 +12,7 @@ const NOOP = () => {};
 export const KeyboardController: KeyboardControllerModule = {
   setDefaultMode: NOOP,
   setInputMode: NOOP,
+  dismiss: NOOP,
   addListener: NOOP,
   removeListeners: NOOP,
 };
diff --git a/src/specs/NativeKeyboardController.ts b/src/specs/NativeKeyboardController.ts
index 0da8d64ce9..489378a983 100644
--- a/src/specs/NativeKeyboardController.ts
+++ b/src/specs/NativeKeyboardController.ts
@@ -8,6 +8,7 @@ export interface Spec extends TurboModule {
   // methods
   setInputMode(mode: number): void;
   setDefaultMode(): void;
+  dismiss(): void;
 
   // event emitter
   addListener: (eventName: string) => void;
diff --git a/src/types.ts b/src/types.ts
index be6297de2f..8dba8d0573 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -85,6 +85,8 @@ export type KeyboardControllerModule = {
   // android only
   setDefaultMode: () => void;
   setInputMode: (mode: number) => void;
+  // all platforms
+  dismiss: () => void;
   // native event module stuff
   addListener: (eventName: string) => void;
   removeListeners: (count: number) => void;