Skip to content

Commit 80d8972

Browse files
authored
feat: async dismiss (#693)
## 📜 Description Make `dismiss` method async to get rid off boilerplate/utilities functions in main project. ## 💡 Motivation and Context Originally this idea was suggested by @terrysahaidak. Before many projects had its own implementation of this method. But @terrysahaidak suggested and I agreed that it would be better to keep this method directly inside the library. ## 📢 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 - include methods signature in methods description; ### JS - changed mock implementation; - make `dismiss` method async; - create `module` file; - set global listeners for `KeyboardEvents` inside `module` file; ## 🤔 How Has This Been Tested? Tested on CI. ## 📝 Checklist - [x] CI successfully passed - [x] I added new mocks and corresponding unit-tests if library API was changed
1 parent 8ba5bb4 commit 80d8972

File tree

11 files changed

+82
-40
lines changed

11 files changed

+82
-40
lines changed

FabricExample/src/screens/Examples/Toolbar/Contacts.tsx

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,7 @@ import {
77
TouchableOpacity,
88
View,
99
} from "react-native";
10-
import {
11-
KeyboardController,
12-
KeyboardEvents,
13-
} from "react-native-keyboard-controller";
10+
import { KeyboardController } from "react-native-keyboard-controller";
1411

1512
export type Contact = {
1613
image: string;
@@ -45,13 +42,9 @@ type Props = {
4542

4643
const AutoFillContacts = ({ onContactSelected }: Props) => {
4744
const [visible, setVisible] = useState(false);
48-
const handlePresentModalPress = useCallback(() => {
49-
const subscription = KeyboardEvents.addListener("keyboardDidHide", () => {
50-
setVisible(true);
51-
subscription.remove();
52-
});
53-
54-
KeyboardController.dismiss();
45+
const handlePresentModalPress = useCallback(async () => {
46+
await KeyboardController.dismiss();
47+
setVisible(true);
5548
}, []);
5649

5750
const handleCloseModalPress = useCallback(() => {

docs/docs/api/keyboard-controller.md

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ The `KeyboardController` module in React Native provides a convenient set of met
2222

2323
### `setInputMode` <div className="label android"></div>
2424

25+
```ts
26+
static setInputMode(mode: AndroidSoftInputModes): void;
27+
```
28+
2529
This method is used to dynamically change the `windowSoftInputMode` (`softwareKeyboardLayoutMode` in Expo terminology) 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`:
2630

2731
```ts
@@ -38,6 +42,10 @@ A combination of `adjustResize` + `edge-to-edge` mode will result in behavior si
3842

3943
### `setDefaultMode` <div className="label android"></div>
4044

45+
```ts
46+
static setDefaultMode(): void;
47+
```
48+
4149
This method is used to restore the default `windowSoftInputMode` (`softwareKeyboardLayoutMode` in Expo terminology) declared in the `AndroidManifest.xml` (or `app.json` in Expo case). It resets the input mode to the default value:
4250

4351
```ts
@@ -46,10 +54,14 @@ KeyboardController.setDefaultMode();
4654

4755
### `dismiss`
4856

49-
This method is used to hide the keyboard. It triggers the dismissal of the keyboard:
57+
```ts
58+
static dismiss(): Promise<void>;
59+
```
60+
61+
This method is used to hide the keyboard. It triggers the dismissal of the keyboard. The method returns promise that will be resolved only when keyboard is fully hidden (if keyboard is already hidden it will resolve immediately):
5062

5163
```ts
52-
KeyboardController.dismiss();
64+
await KeyboardController.dismiss();
5365
```
5466

5567
:::info What is the difference comparing to `react-native` implementation?
@@ -60,12 +72,16 @@ In contrast, the described method enables keyboard dismissal for any focused inp
6072

6173
### `setFocusTo`
6274

75+
```ts
76+
static setFocusTo(direction: "prev" | "current" | "next"): void;
77+
```
78+
6379
This method sets focus to the selected field. Possible values:
6480

6581
- `prev` - set focus to the previous field;
6682
- `current` - set focus to the last focused field (i. e. if keyboard was closed you can restore focus);
6783
- `next` - set focus to the next field.
6884

6985
```ts
70-
setFocusTo("next");
86+
KeyboardController.setFocusTo("next");
7187
```

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

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,7 @@ import {
77
TouchableOpacity,
88
View,
99
} from "react-native";
10-
import {
11-
KeyboardController,
12-
KeyboardEvents,
13-
} from "react-native-keyboard-controller";
10+
import { KeyboardController } from "react-native-keyboard-controller";
1411

1512
export type Contact = {
1613
image: string;
@@ -45,13 +42,9 @@ type Props = {
4542

4643
const AutoFillContacts = ({ onContactSelected }: Props) => {
4744
const [visible, setVisible] = useState(false);
48-
const handlePresentModalPress = useCallback(() => {
49-
const subscription = KeyboardEvents.addListener("keyboardDidHide", () => {
50-
setVisible(true);
51-
subscription.remove();
52-
});
53-
54-
KeyboardController.dismiss();
45+
const handlePresentModalPress = useCallback(async () => {
46+
await KeyboardController.dismiss();
47+
setVisible(true);
5548
}, []);
5649

5750
const handleCloseModalPress = useCallback(() => {

jest/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ const mock = {
4949
KeyboardController: {
5050
setInputMode: jest.fn(),
5151
setDefaultMode: jest.fn(),
52-
dismiss: jest.fn(),
52+
dismiss: jest.fn().mockReturnValue(Promise.resolve()),
5353
setFocusTo: jest.fn(),
5454
},
5555
AndroidSoftInputModes: {

src/bindings.native.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { NativeEventEmitter, Platform } from "react-native";
22

33
import type {
44
FocusedInputEventsModule,
5-
KeyboardControllerModule,
65
KeyboardControllerNativeModule,
76
KeyboardControllerProps,
87
KeyboardEventsModule,
@@ -40,13 +39,7 @@ export const KeyboardEvents: KeyboardEventsModule = {
4039
addListener: (name, cb) =>
4140
eventEmitter.addListener(KEYBOARD_CONTROLLER_NAMESPACE + name, cb),
4241
};
43-
export const KeyboardController: KeyboardControllerModule = {
44-
setDefaultMode: KeyboardControllerNative.setDefaultMode,
45-
setInputMode: KeyboardControllerNative.setInputMode,
46-
setFocusTo: KeyboardControllerNative.setFocusTo,
47-
// additional function is needed because of this https://github.com/kirillzyusko/react-native-keyboard-controller/issues/684
48-
dismiss: () => KeyboardControllerNative.dismiss(),
49-
};
42+
5043
/**
5144
* This API is not documented, it's for internal usage only (for now), and is a subject to potential breaking changes in future.
5245
* Use it with cautious.

src/bindings.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { View } from "react-native";
22

33
import type {
44
FocusedInputEventsModule,
5-
KeyboardControllerModule,
5+
KeyboardControllerNativeModule,
66
KeyboardControllerProps,
77
KeyboardEventsModule,
88
KeyboardGestureAreaProps,
@@ -13,11 +13,13 @@ import type { EmitterSubscription } from "react-native";
1313

1414
const NOOP = () => {};
1515

16-
export const KeyboardController: KeyboardControllerModule = {
16+
export const KeyboardControllerNative: KeyboardControllerNativeModule = {
1717
setDefaultMode: NOOP,
1818
setInputMode: NOOP,
1919
dismiss: NOOP,
2020
setFocusTo: NOOP,
21+
addListener: NOOP,
22+
removeListeners: NOOP,
2123
};
2224
export const KeyboardEvents: KeyboardEventsModule = {
2325
addListener: () => ({ remove: NOOP } as EmitterSubscription),

src/components/KeyboardToolbar/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import React, { useCallback, useEffect, useMemo, useState } from "react";
22
import { StyleSheet, Text, View } from "react-native";
33

4-
import { FocusedInputEvents, KeyboardController } from "../../bindings";
4+
import { FocusedInputEvents } from "../../bindings";
5+
import { KeyboardController } from "../../module";
56
import useColorScheme from "../hooks/useColorScheme";
67
import KeyboardStickyView from "../KeyboardStickyView";
78

src/hooks/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { useEffect, useLayoutEffect } from "react";
22

3-
import { KeyboardController } from "../bindings";
43
import { AndroidSoftInputModes } from "../constants";
54
import { useKeyboardContext } from "../context";
5+
import { KeyboardController } from "../module";
66

77
import type { AnimatedContext, ReanimatedContext } from "../context";
88
import type { FocusedInputHandler, KeyboardHandler } from "../types";

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export * from "./animated";
33
export * from "./context";
44
export * from "./hooks";
55
export * from "./constants";
6+
export * from "./module";
67
export * from "./types";
78

89
export {

src/module.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { KeyboardControllerNative, KeyboardEvents } from "./bindings";
2+
3+
import type { KeyboardControllerModule } from "./types";
4+
5+
let isVisible = false;
6+
7+
KeyboardEvents.addListener("keyboardDidHide", () => {
8+
isVisible = false;
9+
});
10+
11+
KeyboardEvents.addListener("keyboardDidShow", () => {
12+
isVisible = true;
13+
});
14+
15+
const dismiss = async (): Promise<void> => {
16+
return new Promise((resolve) => {
17+
if (!isVisible) {
18+
resolve();
19+
20+
return;
21+
}
22+
23+
const subscription = KeyboardEvents.addListener("keyboardDidHide", () => {
24+
resolve(undefined);
25+
subscription.remove();
26+
});
27+
28+
KeyboardControllerNative.dismiss();
29+
});
30+
};
31+
32+
export const KeyboardController: KeyboardControllerModule = {
33+
setDefaultMode: KeyboardControllerNative.setDefaultMode,
34+
setInputMode: KeyboardControllerNative.setInputMode,
35+
setFocusTo: KeyboardControllerNative.setFocusTo,
36+
dismiss: dismiss,
37+
};

0 commit comments

Comments
 (0)