UNPKG

3.52 kBTypeScriptView Raw
1import * as React from 'react';
2import { HostComponent, Keyboard, TextInput } from 'react-native';
3
4type InputRef = React.ElementRef<HostComponent<unknown>> | undefined;
5
6export default function useKeyboardManager(isEnabled: () => boolean) {
7 // Numeric id of the previously focused text input
8 // When a gesture didn't change the tab, we can restore the focused input with this
9 const previouslyFocusedTextInputRef = React.useRef<InputRef>(undefined);
10 const startTimestampRef = React.useRef<number>(0);
11 const keyboardTimeoutRef = React.useRef<any>();
12
13 const clearKeyboardTimeout = React.useCallback(() => {
14 if (keyboardTimeoutRef.current !== undefined) {
15 clearTimeout(keyboardTimeoutRef.current);
16 keyboardTimeoutRef.current = undefined;
17 }
18 }, []);
19
20 const onPageChangeStart = React.useCallback(() => {
21 if (!isEnabled()) {
22 return;
23 }
24
25 clearKeyboardTimeout();
26
27 const input: InputRef = TextInput.State.currentlyFocusedInput();
28
29 // When a page change begins, blur the currently focused input
30 input?.blur();
31
32 // Store the id of this input so we can refocus it if change was cancelled
33 previouslyFocusedTextInputRef.current = input;
34
35 // Store timestamp for touch start
36 startTimestampRef.current = Date.now();
37 }, [clearKeyboardTimeout, isEnabled]);
38
39 const onPageChangeConfirm = React.useCallback(
40 (force: boolean) => {
41 if (!isEnabled()) {
42 return;
43 }
44
45 clearKeyboardTimeout();
46
47 if (force) {
48 // Always dismiss input, even if we don't have a ref to it
49 // We might not have the ref if onPageChangeStart was never called
50 // This can happen if page change was not from a gesture
51 Keyboard.dismiss();
52 } else {
53 const input = previouslyFocusedTextInputRef.current;
54
55 // Dismiss the keyboard only if an input was a focused before
56 // This makes sure we don't dismiss input on going back and focusing an input
57 input?.blur();
58 }
59
60 // Cleanup the ID on successful page change
61 previouslyFocusedTextInputRef.current = undefined;
62 },
63 [clearKeyboardTimeout, isEnabled]
64 );
65
66 const onPageChangeCancel = React.useCallback(() => {
67 if (!isEnabled()) {
68 return;
69 }
70
71 clearKeyboardTimeout();
72
73 // The page didn't change, we should restore the focus of text input
74 const input = previouslyFocusedTextInputRef.current;
75
76 if (input) {
77 // If the interaction was super short we should make sure keyboard won't hide again.
78
79 // Too fast input refocus will result only in keyboard flashing on screen and hiding right away.
80 // During first ~100ms keyboard will be dismissed no matter what,
81 // so we have to make sure it won't interrupt input refocus logic.
82 // That's why when the interaction is shorter than 100ms we add delay so it won't hide once again.
83 // Subtracting timestamps makes us sure the delay is executed only when needed.
84 if (Date.now() - startTimestampRef.current < 100) {
85 keyboardTimeoutRef.current = setTimeout(() => {
86 input?.focus();
87 previouslyFocusedTextInputRef.current = undefined;
88 }, 100);
89 } else {
90 input?.focus();
91 previouslyFocusedTextInputRef.current = undefined;
92 }
93 }
94 }, [clearKeyboardTimeout, isEnabled]);
95
96 React.useEffect(() => {
97 return () => clearKeyboardTimeout();
98 }, [clearKeyboardTimeout]);
99
100 return {
101 onPageChangeStart,
102 onPageChangeConfirm,
103 onPageChangeCancel,
104 };
105}