Skip to content

Commit f38a107

Browse files
committed
feat: dismiss without animation
1 parent 8896503 commit f38a107

8 files changed

Lines changed: 62 additions & 29 deletions

File tree

FabricExample/src/screens/Examples/Close/index.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ function CloseScreen() {
1010

1111
const ref = useRef<TextInput>(null);
1212
const [keepFocus, setKeepFocus] = useState(false);
13+
const [animated, setAnimated] = useState(true);
1314

1415
return (
1516
<View>
@@ -18,10 +19,15 @@ function CloseScreen() {
1819
title={keepFocus ? "Keep focus" : "Don't keep focus"}
1920
onPress={() => setKeepFocus(!keepFocus)}
2021
/>
22+
<Button
23+
testID="animated_button"
24+
title={animated ? "Animated" : "Instant"}
25+
onPress={() => setAnimated(!animated)}
26+
/>
2127
<Button
2228
testID="set_focus_to_current"
2329
title="KeyboardController.setFocusTo('current')"
24-
onPress={() => KeyboardController.setFocusTo("current")}
30+
onPress={() => setAnimated(!animated)}
2531
/>
2632
<Button
2733
testID="focus_from_ref"
@@ -36,7 +42,7 @@ function CloseScreen() {
3642
<Button
3743
testID="close_keyboard_button"
3844
title="Close keyboard"
39-
onPress={() => KeyboardController.dismiss({ keepFocus })}
45+
onPress={() => KeyboardController.dismiss({ keepFocus, animated })}
4046
/>
4147
<TextInput
4248
ref={ref}

docs/docs/api/keyboard-controller.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,12 @@ If you want to hide a keyboard and keep focus then you can pass `keepFocus` opti
8282
await KeyboardController.dismiss({ keepFocus: true });
8383
```
8484

85+
If you want to hide keyboard immediately (i. e. without animation), you can pass `animated` option:
86+
87+
```ts
88+
await KeyboardController.dismiss({ animated: false });
89+
```
90+
8591
:::info What is the difference comparing to `react-native` implementation?
8692
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.
8793

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ function CloseScreen() {
1010

1111
const ref = useRef<TextInput>(null);
1212
const [keepFocus, setKeepFocus] = useState(false);
13+
const [animated, setAnimated] = useState(true);
1314

1415
return (
1516
<View>
@@ -18,10 +19,15 @@ function CloseScreen() {
1819
title={keepFocus ? "Keep focus" : "Don't keep focus"}
1920
onPress={() => setKeepFocus(!keepFocus)}
2021
/>
22+
<Button
23+
testID="animated_button"
24+
title={animated ? "Animated" : "Instant"}
25+
onPress={() => setAnimated(!animated)}
26+
/>
2127
<Button
2228
testID="set_focus_to_current"
2329
title="KeyboardController.setFocusTo('current')"
24-
onPress={() => KeyboardController.setFocusTo("current")}
30+
onPress={() => setAnimated(!animated)}
2531
/>
2632
<Button
2733
testID="focus_from_ref"
@@ -36,7 +42,7 @@ function CloseScreen() {
3642
<Button
3743
testID="close_keyboard_button"
3844
title="Close keyboard"
39-
onPress={() => KeyboardController.dismiss({ keepFocus })}
45+
onPress={() => KeyboardController.dismiss({ keepFocus, animated })}
4046
/>
4147
<TextInput
4248
ref={ref}

ios/KeyboardControllerModule.mm

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,13 @@ - (void)preload
7777
}
7878

7979
#ifdef RCT_NEW_ARCH_ENABLED
80-
- (void)dismiss:(BOOL)keepFocus
80+
- (void)dismiss:(BOOL)keepFocus animated:(BOOL)animated
8181
#else
82-
RCT_EXPORT_METHOD(dismiss : (BOOL)keepFocus)
82+
RCT_EXPORT_METHOD(dismiss:(BOOL)keepFocus animated:(BOOL)animated)
8383
#endif
8484
{
8585
dispatch_async(dispatch_get_main_queue(), ^{
86-
[KeyboardControllerModuleImpl dismiss:keepFocus];
86+
[KeyboardControllerModuleImpl dismiss:keepFocus animated:animated];
8787
});
8888
}
8989

ios/KeyboardControllerModuleImpl.swift

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,26 +13,36 @@ public class KeyboardControllerModuleImpl: NSObject {
1313
private static let keyboardRevealGestureName = "keyboardRevealGesture"
1414

1515
@objc
16-
public static func dismiss(_ keepFocus: Bool) {
17-
let responder = UIResponder.current
18-
19-
if keepFocus {
20-
guard let input = responder as? TextInput else { return }
21-
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(onTextInputTapped(_:)))
22-
tapGesture.name = keyboardRevealGestureName
23-
input.addGestureRecognizer(tapGesture)
24-
25-
input.inputView = UIView()
26-
input.reloadInputViews()
27-
28-
NotificationCenter.default.addObserver(
29-
self,
30-
selector: #selector(onResponderResigned(_:)),
31-
name: UITextField.textDidEndEditingNotification,
32-
object: input
33-
)
16+
public static func dismiss(_ keepFocus: Bool, animated: Bool) {
17+
let work = {
18+
let responder = UIResponder.current
19+
20+
if keepFocus {
21+
guard let input = responder as? TextInput else { return }
22+
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(onTextInputTapped(_:)))
23+
tapGesture.name = keyboardRevealGestureName
24+
input.addGestureRecognizer(tapGesture)
25+
26+
input.inputView = UIView()
27+
input.reloadInputViews()
28+
29+
NotificationCenter.default.addObserver(
30+
self,
31+
selector: #selector(onResponderResigned(_:)),
32+
name: UITextField.textDidEndEditingNotification,
33+
object: input
34+
)
35+
} else {
36+
responder?.resignFirstResponder()
37+
}
38+
}
39+
40+
if !animated {
41+
UIView.performWithoutAnimation {
42+
work()
43+
}
3444
} else {
35-
responder?.resignFirstResponder()
45+
work()
3646
}
3747
}
3848

src/module.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@ KeyboardEvents.addListener("keyboardDidShow", (e) => {
2626
lastState = e;
2727
});
2828

29-
const dismiss = async (options?: DismissOptions): Promise<void> => {
29+
const dismiss = async (options?: Partial<DismissOptions>): Promise<void> => {
3030
const keepFocus = options?.keepFocus ?? false;
31+
const animated = options?.animated ?? true;
3132

3233
return new Promise((resolve) => {
3334
if (isClosed) {
@@ -41,7 +42,7 @@ const dismiss = async (options?: DismissOptions): Promise<void> => {
4142
subscription.remove();
4243
});
4344

44-
KeyboardControllerNative.dismiss(keepFocus);
45+
KeyboardControllerNative.dismiss(keepFocus, animated);
4546
});
4647
};
4748
const isVisible = () => !isClosed;

src/specs/NativeKeyboardController.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export interface Spec extends TurboModule {
99
setInputMode(mode: number): void;
1010
setDefaultMode(): void;
1111
preload(): void;
12-
dismiss(keepFocus: boolean): void;
12+
dismiss(keepFocus: boolean, animated: boolean): void;
1313
setFocusTo(direction: string): void;
1414

1515
// event emitter

src/types/module.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ export type DismissOptions = {
5353
* A boolean property indicating whether focus should be kept on the input after dismissing the keyboard. Default is `false`.
5454
*/
5555
keepFocus: boolean;
56+
/**
57+
* A boolean property controlling whether dismissal should be animated. Default is `true`.
58+
*/
59+
animated: boolean;
5660
};
5761
export type KeyboardControllerModule = {
5862
// android only

0 commit comments

Comments
 (0)