react-native-keyboard-controller 1.10.6 → 1.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/android/build.gradle +6 -0
- package/android/src/fabric/java/com/reactnativekeyboardcontroller/KeyboardControllerModule.kt +4 -0
- package/android/src/main/java/com/reactnativekeyboardcontroller/events/FocusedInputLayoutChangedEvent.kt +2 -0
- package/android/src/main/java/com/reactnativekeyboardcontroller/extensions/EditText.kt +22 -0
- package/android/src/main/java/com/reactnativekeyboardcontroller/extensions/ThemedReactContext.kt +9 -0
- package/android/src/main/java/com/reactnativekeyboardcontroller/listeners/FocusedInputObserver.kt +20 -0
- package/android/src/main/java/com/reactnativekeyboardcontroller/listeners/KeyboardAnimationCallback.kt +7 -13
- package/android/src/main/java/com/reactnativekeyboardcontroller/modules/KeyboardControllerModuleImpl.kt +15 -0
- package/android/src/main/java/com/reactnativekeyboardcontroller/traversal/FocusedInputHolder.kt +16 -0
- package/android/src/main/java/com/reactnativekeyboardcontroller/traversal/ViewHierarchyNavigator.kt +101 -0
- package/android/src/paper/java/com/reactnativekeyboardcontroller/KeyboardControllerModule.kt +5 -0
- package/android/src/test/java/com/reactnativekeyboardcontroller/traversal/ViewHierarchyNavigatorTest.kt +188 -0
- package/ios/.swiftlint.yml +4 -0
- package/ios/Extensions.swift +29 -0
- package/ios/KeyboardController.xcodeproj/project.pbxproj +20 -0
- package/ios/KeyboardControllerModule.mm +18 -0
- package/ios/events/FocusedInputLayoutChangedEvent.m +3 -0
- package/ios/observers/FocusedInputObserver.swift +20 -4
- package/ios/observers/KeyboardMovementObserver.swift +5 -4
- package/ios/traversal/FocusedInputHolder.swift +32 -0
- package/ios/traversal/TextInput.swift +26 -0
- package/ios/traversal/ViewHierarchyNavigator.swift +116 -0
- package/ios/views/KeyboardControllerView.mm +5 -0
- package/ios/views/KeyboardControllerViewManager.swift +4 -1
- package/jest/index.js +20 -0
- package/lib/commonjs/bindings.js +12 -1
- package/lib/commonjs/bindings.js.map +1 -1
- package/lib/commonjs/bindings.native.js +11 -2
- package/lib/commonjs/bindings.native.js.map +1 -1
- package/lib/commonjs/components/KeyboardAwareScrollView/index.js +18 -6
- package/lib/commonjs/components/KeyboardAwareScrollView/index.js.map +1 -1
- package/lib/commonjs/components/KeyboardToolbar/Arrow.js +79 -0
- package/lib/commonjs/components/KeyboardToolbar/Arrow.js.map +1 -0
- package/lib/commonjs/components/KeyboardToolbar/Button.js +74 -0
- package/lib/commonjs/components/KeyboardToolbar/Button.js.map +1 -0
- package/lib/commonjs/components/KeyboardToolbar/colors.js +22 -0
- package/lib/commonjs/components/KeyboardToolbar/colors.js.map +1 -0
- package/lib/commonjs/components/KeyboardToolbar/colors.native.js +41 -0
- package/lib/commonjs/components/KeyboardToolbar/colors.native.js.map +1 -0
- package/lib/commonjs/components/KeyboardToolbar/index.js +136 -0
- package/lib/commonjs/components/KeyboardToolbar/index.js.map +1 -0
- package/lib/commonjs/components/KeyboardToolbar/types.js +6 -0
- package/lib/commonjs/components/KeyboardToolbar/types.js.map +1 -0
- package/lib/commonjs/components/hooks/useColorScheme.js +11 -0
- package/lib/commonjs/components/hooks/useColorScheme.js.map +1 -0
- package/lib/commonjs/components/hooks/useKeyboardInterpolation.js +3 -2
- package/lib/commonjs/components/hooks/useKeyboardInterpolation.js.map +1 -1
- package/lib/commonjs/components/index.js +21 -0
- package/lib/commonjs/components/index.js.map +1 -1
- package/lib/commonjs/index.js +22 -13
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/specs/KeyboardControllerViewNativeComponent.js.map +1 -1
- package/lib/commonjs/specs/NativeKeyboardController.js.map +1 -1
- package/lib/commonjs/types.js.map +1 -1
- package/lib/module/bindings.js +10 -0
- package/lib/module/bindings.js.map +1 -1
- package/lib/module/bindings.native.js +9 -1
- package/lib/module/bindings.native.js.map +1 -1
- package/lib/module/components/KeyboardAwareScrollView/index.js +18 -6
- package/lib/module/components/KeyboardAwareScrollView/index.js.map +1 -1
- package/lib/module/components/KeyboardToolbar/Arrow.js +69 -0
- package/lib/module/components/KeyboardToolbar/Arrow.js.map +1 -0
- package/lib/module/components/KeyboardToolbar/Button.js +64 -0
- package/lib/module/components/KeyboardToolbar/Button.js.map +1 -0
- package/lib/module/components/KeyboardToolbar/colors.js +15 -0
- package/lib/module/components/KeyboardToolbar/colors.js.map +1 -0
- package/lib/module/components/KeyboardToolbar/colors.native.js +34 -0
- package/lib/module/components/KeyboardToolbar/colors.native.js.map +1 -0
- package/lib/module/components/KeyboardToolbar/index.js +121 -0
- package/lib/module/components/KeyboardToolbar/index.js.map +1 -0
- package/lib/module/components/KeyboardToolbar/types.js +2 -0
- package/lib/module/components/KeyboardToolbar/types.js.map +1 -0
- package/lib/module/components/hooks/useColorScheme.js +4 -0
- package/lib/module/components/hooks/useColorScheme.js.map +1 -0
- package/lib/module/components/hooks/useKeyboardInterpolation.js +3 -2
- package/lib/module/components/hooks/useKeyboardInterpolation.js.map +1 -1
- package/lib/module/components/index.js +1 -0
- package/lib/module/components/index.js.map +1 -1
- package/lib/module/index.js +3 -2
- package/lib/module/index.js.map +1 -1
- package/lib/module/specs/KeyboardControllerViewNativeComponent.js.map +1 -1
- package/lib/module/specs/NativeKeyboardController.js.map +1 -1
- package/lib/module/types.js.map +1 -1
- package/lib/typescript/bindings.d.ts +6 -1
- package/lib/typescript/bindings.native.d.ts +6 -1
- package/lib/typescript/components/KeyboardToolbar/Arrow.d.ts +9 -0
- package/lib/typescript/components/KeyboardToolbar/Button.d.ts +15 -0
- package/lib/typescript/components/KeyboardToolbar/colors.d.ts +2 -0
- package/lib/typescript/components/KeyboardToolbar/colors.native.d.ts +2 -0
- package/lib/typescript/components/KeyboardToolbar/index.d.ts +25 -0
- package/lib/typescript/components/KeyboardToolbar/types.d.ts +16 -0
- package/lib/typescript/components/hooks/useColorScheme.d.ts +2 -0
- package/lib/typescript/components/index.d.ts +1 -0
- package/lib/typescript/index.d.ts +1 -2
- package/lib/typescript/specs/KeyboardControllerViewNativeComponent.d.ts +1 -0
- package/lib/typescript/specs/NativeKeyboardController.d.ts +1 -0
- package/lib/typescript/types.d.ts +11 -0
- package/package.json +5 -1
- package/src/bindings.native.ts +11 -1
- package/src/bindings.ts +9 -0
- package/src/components/KeyboardAwareScrollView/index.tsx +22 -2
- package/src/components/KeyboardToolbar/Arrow.tsx +80 -0
- package/src/components/KeyboardToolbar/Button.tsx +98 -0
- package/src/components/KeyboardToolbar/colors.native.ts +37 -0
- package/src/components/KeyboardToolbar/colors.ts +16 -0
- package/src/components/KeyboardToolbar/index.tsx +154 -0
- package/src/components/KeyboardToolbar/types.ts +16 -0
- package/src/components/hooks/useColorScheme.ts +5 -0
- package/src/components/hooks/useKeyboardInterpolation.ts +3 -2
- package/src/components/index.ts +5 -0
- package/src/index.ts +4 -1
- package/src/specs/KeyboardControllerViewNativeComponent.ts +1 -0
- package/src/specs/NativeKeyboardController.ts +1 -0
- package/src/types.ts +14 -0
- package/lib/commonjs/replicas.js +0 -125
- package/lib/commonjs/replicas.js.map +0 -1
- package/lib/module/replicas.js +0 -115
- package/lib/module/replicas.js.map +0 -1
- package/lib/typescript/replicas.d.ts +0 -38
- package/src/replicas.ts +0 -154
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-keyboard-controller",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.1",
|
|
4
4
|
"description": "Keyboard manager which works in identical way on both iOS and Android",
|
|
5
5
|
"main": "lib/commonjs/index",
|
|
6
6
|
"module": "lib/module/index",
|
|
@@ -48,6 +48,10 @@
|
|
|
48
48
|
"avoiding view",
|
|
49
49
|
"sticky view",
|
|
50
50
|
"keyboard aware scroll view",
|
|
51
|
+
"keyboard toolbar",
|
|
52
|
+
"keyboard done button",
|
|
53
|
+
"keyboard next button",
|
|
54
|
+
"keyboard previous button",
|
|
51
55
|
"ios",
|
|
52
56
|
"android"
|
|
53
57
|
],
|
package/src/bindings.native.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { NativeEventEmitter, Platform } from "react-native";
|
|
2
2
|
|
|
3
3
|
import type {
|
|
4
|
+
FocusedInputEventsModule,
|
|
4
5
|
KeyboardControllerModule,
|
|
5
6
|
KeyboardControllerProps,
|
|
6
7
|
KeyboardEventsModule,
|
|
@@ -28,11 +29,20 @@ export const KeyboardController = (
|
|
|
28
29
|
)
|
|
29
30
|
) as KeyboardControllerModule;
|
|
30
31
|
|
|
32
|
+
const KEYBOARD_CONTROLLER_NAMESPACE = "KeyboardController::";
|
|
31
33
|
const eventEmitter = new NativeEventEmitter(KeyboardController);
|
|
32
34
|
|
|
33
35
|
export const KeyboardEvents: KeyboardEventsModule = {
|
|
34
36
|
addListener: (name, cb) =>
|
|
35
|
-
eventEmitter.addListener(
|
|
37
|
+
eventEmitter.addListener(KEYBOARD_CONTROLLER_NAMESPACE + name, cb),
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* This API is not documented, it's for internal usage only (for now), and is a subject to potential breaking changes in future.
|
|
41
|
+
* Use it with cautious.
|
|
42
|
+
*/
|
|
43
|
+
export const FocusedInputEvents: FocusedInputEventsModule = {
|
|
44
|
+
addListener: (name, cb) =>
|
|
45
|
+
eventEmitter.addListener(KEYBOARD_CONTROLLER_NAMESPACE + name, cb),
|
|
36
46
|
};
|
|
37
47
|
export const KeyboardControllerView: React.FC<KeyboardControllerProps> =
|
|
38
48
|
require("./specs/KeyboardControllerViewNativeComponent").default;
|
package/src/bindings.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { View } from "react-native";
|
|
2
2
|
|
|
3
3
|
import type {
|
|
4
|
+
FocusedInputEventsModule,
|
|
4
5
|
KeyboardControllerModule,
|
|
5
6
|
KeyboardControllerProps,
|
|
6
7
|
KeyboardEventsModule,
|
|
@@ -13,12 +14,20 @@ export const KeyboardController: KeyboardControllerModule = {
|
|
|
13
14
|
setDefaultMode: NOOP,
|
|
14
15
|
setInputMode: NOOP,
|
|
15
16
|
dismiss: NOOP,
|
|
17
|
+
setFocusTo: NOOP,
|
|
16
18
|
addListener: NOOP,
|
|
17
19
|
removeListeners: NOOP,
|
|
18
20
|
};
|
|
19
21
|
export const KeyboardEvents: KeyboardEventsModule = {
|
|
20
22
|
addListener: () => ({ remove: NOOP } as EmitterSubscription),
|
|
21
23
|
};
|
|
24
|
+
/**
|
|
25
|
+
* This API is not documented, it's for internal usage only (for now), and is a subject to potential breaking changes in future.
|
|
26
|
+
* Use it with cautious.
|
|
27
|
+
*/
|
|
28
|
+
export const FocusedInputEvents: FocusedInputEventsModule = {
|
|
29
|
+
addListener: () => ({ remove: NOOP } as EmitterSubscription),
|
|
30
|
+
};
|
|
22
31
|
export const KeyboardControllerView =
|
|
23
32
|
View as unknown as React.FC<KeyboardControllerProps>;
|
|
24
33
|
export const KeyboardGestureArea =
|
|
@@ -18,7 +18,11 @@ import {
|
|
|
18
18
|
import { useSmoothKeyboardHandler } from "./useSmoothKeyboardHandler";
|
|
19
19
|
import { debounce } from "./utils";
|
|
20
20
|
|
|
21
|
-
import type {
|
|
21
|
+
import type {
|
|
22
|
+
LayoutChangeEvent,
|
|
23
|
+
ScrollView,
|
|
24
|
+
ScrollViewProps,
|
|
25
|
+
} from "react-native";
|
|
22
26
|
import type { FocusedInputLayoutChangedEvent } from "react-native-keyboard-controller";
|
|
23
27
|
|
|
24
28
|
type KeyboardAwareScrollViewProps = {
|
|
@@ -75,6 +79,7 @@ const KeyboardAwareScrollView = forwardRef<
|
|
|
75
79
|
(
|
|
76
80
|
{
|
|
77
81
|
children,
|
|
82
|
+
onLayout,
|
|
78
83
|
bottomOffset = 0,
|
|
79
84
|
disableScrollOnKeyboardHide = false,
|
|
80
85
|
enabled = true,
|
|
@@ -83,6 +88,7 @@ const KeyboardAwareScrollView = forwardRef<
|
|
|
83
88
|
ref,
|
|
84
89
|
) => {
|
|
85
90
|
const scrollViewAnimatedRef = useAnimatedRef<Reanimated.ScrollView>();
|
|
91
|
+
const scrollViewTarget = useSharedValue<number | null>(null);
|
|
86
92
|
const scrollPosition = useSharedValue(0);
|
|
87
93
|
const position = useSharedValue(0);
|
|
88
94
|
const currentKeyboardFrameHeight = useSharedValue(0);
|
|
@@ -114,6 +120,14 @@ const KeyboardAwareScrollView = forwardRef<
|
|
|
114
120
|
|
|
115
121
|
scrollViewAnimatedRef(assignedRef);
|
|
116
122
|
}, []);
|
|
123
|
+
const onScrollViewLayout = useCallback(
|
|
124
|
+
(e: LayoutChangeEvent & { nativeEvent: { target: number } }) => {
|
|
125
|
+
scrollViewTarget.value = e.nativeEvent.target;
|
|
126
|
+
|
|
127
|
+
onLayout?.(e);
|
|
128
|
+
},
|
|
129
|
+
[onLayout],
|
|
130
|
+
);
|
|
117
131
|
|
|
118
132
|
/**
|
|
119
133
|
* Function that will scroll a ScrollView as keyboard gets moving
|
|
@@ -126,6 +140,11 @@ const KeyboardAwareScrollView = forwardRef<
|
|
|
126
140
|
return 0;
|
|
127
141
|
}
|
|
128
142
|
|
|
143
|
+
// input belongs to ScrollView
|
|
144
|
+
if (layout.value?.parentScrollViewTarget !== scrollViewTarget.value) {
|
|
145
|
+
return 0;
|
|
146
|
+
}
|
|
147
|
+
|
|
129
148
|
const visibleRect = height - keyboardHeight.value;
|
|
130
149
|
const absoluteY = layout.value?.layout.absoluteY || 0;
|
|
131
150
|
const inputHeight = layout.value?.layout.height || 0;
|
|
@@ -300,7 +319,8 @@ const KeyboardAwareScrollView = forwardRef<
|
|
|
300
319
|
<Reanimated.ScrollView
|
|
301
320
|
ref={onRef}
|
|
302
321
|
{...rest}
|
|
303
|
-
// @ts-expect-error
|
|
322
|
+
// @ts-expect-error https://github.com/facebook/react-native/pull/42785
|
|
323
|
+
onLayout={onScrollViewLayout}
|
|
304
324
|
onScrollReanimated={onScroll}
|
|
305
325
|
scrollEventThrottle={16}
|
|
306
326
|
>
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import React, { useMemo } from "react";
|
|
2
|
+
import { Animated, StyleSheet, View } from "react-native";
|
|
3
|
+
|
|
4
|
+
import useColorScheme from "../hooks/useColorScheme";
|
|
5
|
+
|
|
6
|
+
import type { KeyboardToolbarTheme } from "./types";
|
|
7
|
+
import type { ViewStyle } from "react-native";
|
|
8
|
+
|
|
9
|
+
type ArrowProps = {
|
|
10
|
+
type: "prev" | "next";
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
theme: KeyboardToolbarTheme;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const ArrowComponent: React.FC<ArrowProps> = ({ type, disabled, theme }) => {
|
|
16
|
+
const colorScheme = useColorScheme();
|
|
17
|
+
|
|
18
|
+
const color = useMemo(
|
|
19
|
+
() => ({
|
|
20
|
+
backgroundColor: disabled
|
|
21
|
+
? theme[colorScheme].disabled
|
|
22
|
+
: theme[colorScheme].primary,
|
|
23
|
+
}),
|
|
24
|
+
[disabled, theme, colorScheme],
|
|
25
|
+
);
|
|
26
|
+
const left = useMemo(() => [styles.arrowLeftLine, color], [color]);
|
|
27
|
+
const right = useMemo(() => [styles.arrowRightLine, color], [color]);
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<View
|
|
31
|
+
style={
|
|
32
|
+
type === "next" ? styles.arrowDownContainer : styles.arrowUpContainer
|
|
33
|
+
}
|
|
34
|
+
>
|
|
35
|
+
<View style={styles.arrow}>
|
|
36
|
+
<Animated.View style={left} />
|
|
37
|
+
<Animated.View style={right} />
|
|
38
|
+
</View>
|
|
39
|
+
</View>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const arrowLine: ViewStyle = {
|
|
44
|
+
width: 13,
|
|
45
|
+
height: 2,
|
|
46
|
+
borderRadius: 1,
|
|
47
|
+
};
|
|
48
|
+
const arrowUpContainer: ViewStyle = {
|
|
49
|
+
marginHorizontal: 5,
|
|
50
|
+
width: 30,
|
|
51
|
+
height: 30,
|
|
52
|
+
justifyContent: "center",
|
|
53
|
+
alignItems: "center",
|
|
54
|
+
};
|
|
55
|
+
const styles = StyleSheet.create({
|
|
56
|
+
arrowUpContainer: arrowUpContainer,
|
|
57
|
+
arrowDownContainer: {
|
|
58
|
+
...arrowUpContainer,
|
|
59
|
+
transform: [{ rotate: "180deg" }],
|
|
60
|
+
},
|
|
61
|
+
arrow: {
|
|
62
|
+
width: 20,
|
|
63
|
+
height: 20,
|
|
64
|
+
flexDirection: "row",
|
|
65
|
+
alignItems: "center",
|
|
66
|
+
justifyContent: "space-between",
|
|
67
|
+
},
|
|
68
|
+
arrowLeftLine: {
|
|
69
|
+
...arrowLine,
|
|
70
|
+
transform: [{ rotate: "-45deg" }],
|
|
71
|
+
left: -0.5,
|
|
72
|
+
},
|
|
73
|
+
arrowRightLine: {
|
|
74
|
+
...arrowLine,
|
|
75
|
+
transform: [{ rotate: "45deg" }],
|
|
76
|
+
left: -5.5,
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
export default ArrowComponent;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import React, { useMemo } from "react";
|
|
2
|
+
import {
|
|
3
|
+
Platform,
|
|
4
|
+
TouchableNativeFeedback,
|
|
5
|
+
TouchableOpacity,
|
|
6
|
+
View,
|
|
7
|
+
} from "react-native";
|
|
8
|
+
|
|
9
|
+
import useColorScheme from "../hooks/useColorScheme";
|
|
10
|
+
|
|
11
|
+
import type { KeyboardToolbarTheme } from "./types";
|
|
12
|
+
import type { PropsWithChildren } from "react";
|
|
13
|
+
import type { ViewStyle } from "react-native";
|
|
14
|
+
|
|
15
|
+
type ButtonProps = {
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
onPress: () => void;
|
|
18
|
+
accessibilityLabel: string;
|
|
19
|
+
accessibilityHint: string;
|
|
20
|
+
testID: string;
|
|
21
|
+
rippleRadius?: number;
|
|
22
|
+
style?: ViewStyle;
|
|
23
|
+
theme: KeyboardToolbarTheme;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const ButtonIOS = ({
|
|
27
|
+
children,
|
|
28
|
+
onPress,
|
|
29
|
+
disabled,
|
|
30
|
+
accessibilityLabel,
|
|
31
|
+
accessibilityHint,
|
|
32
|
+
testID,
|
|
33
|
+
style,
|
|
34
|
+
}: PropsWithChildren<ButtonProps>) => {
|
|
35
|
+
// immediately switch to plain view to avoid animation flickering
|
|
36
|
+
// when fade out animation happens and view becomes disabled
|
|
37
|
+
const Container = disabled
|
|
38
|
+
? (View as unknown as typeof TouchableOpacity)
|
|
39
|
+
: TouchableOpacity;
|
|
40
|
+
const accessibilityState = useMemo(() => ({ disabled }), [disabled]);
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<Container
|
|
44
|
+
accessibilityState={accessibilityState}
|
|
45
|
+
accessibilityRole="button"
|
|
46
|
+
accessibilityLabel={accessibilityLabel}
|
|
47
|
+
accessibilityHint={accessibilityHint}
|
|
48
|
+
onPress={onPress}
|
|
49
|
+
style={style}
|
|
50
|
+
testID={testID}
|
|
51
|
+
>
|
|
52
|
+
{children}
|
|
53
|
+
</Container>
|
|
54
|
+
);
|
|
55
|
+
};
|
|
56
|
+
const ButtonAndroid = ({
|
|
57
|
+
children,
|
|
58
|
+
onPress,
|
|
59
|
+
disabled,
|
|
60
|
+
accessibilityLabel,
|
|
61
|
+
accessibilityHint,
|
|
62
|
+
testID,
|
|
63
|
+
rippleRadius = 18,
|
|
64
|
+
style,
|
|
65
|
+
theme,
|
|
66
|
+
}: PropsWithChildren<ButtonProps>) => {
|
|
67
|
+
const colorScheme = useColorScheme();
|
|
68
|
+
const accessibilityState = useMemo(() => ({ disabled }), [disabled]);
|
|
69
|
+
const ripple = useMemo(
|
|
70
|
+
() =>
|
|
71
|
+
TouchableNativeFeedback.Ripple(
|
|
72
|
+
theme[colorScheme].ripple,
|
|
73
|
+
true,
|
|
74
|
+
rippleRadius,
|
|
75
|
+
),
|
|
76
|
+
[colorScheme, rippleRadius, theme],
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<TouchableNativeFeedback
|
|
81
|
+
accessibilityState={accessibilityState}
|
|
82
|
+
accessibilityRole="button"
|
|
83
|
+
accessibilityLabel={accessibilityLabel}
|
|
84
|
+
accessibilityHint={accessibilityHint}
|
|
85
|
+
onPress={onPress}
|
|
86
|
+
background={ripple}
|
|
87
|
+
testID={testID}
|
|
88
|
+
style={style}
|
|
89
|
+
>
|
|
90
|
+
<View style={style}>{children}</View>
|
|
91
|
+
</TouchableNativeFeedback>
|
|
92
|
+
);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export default Platform.select({
|
|
96
|
+
android: ButtonAndroid,
|
|
97
|
+
default: ButtonIOS,
|
|
98
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Platform, PlatformColor } from "react-native";
|
|
2
|
+
|
|
3
|
+
import type { KeyboardToolbarTheme } from "./types";
|
|
4
|
+
import type { ColorValue } from "react-native";
|
|
5
|
+
|
|
6
|
+
export const colors: KeyboardToolbarTheme = {
|
|
7
|
+
light: {
|
|
8
|
+
primary: Platform.select<ColorValue>({
|
|
9
|
+
ios: PlatformColor("link"),
|
|
10
|
+
default: "#2c2c2c",
|
|
11
|
+
}),
|
|
12
|
+
disabled: Platform.select<ColorValue>({
|
|
13
|
+
ios: PlatformColor("systemGray4"),
|
|
14
|
+
default: "#B0BEC5",
|
|
15
|
+
}),
|
|
16
|
+
background: Platform.select({
|
|
17
|
+
ios: "#F8F8F8",
|
|
18
|
+
default: "#f3f3f4",
|
|
19
|
+
}),
|
|
20
|
+
ripple: "#bcbcbcbc",
|
|
21
|
+
},
|
|
22
|
+
dark: {
|
|
23
|
+
primary: Platform.select<ColorValue>({
|
|
24
|
+
ios: PlatformColor("label"),
|
|
25
|
+
default: "#fafafa",
|
|
26
|
+
}),
|
|
27
|
+
disabled: Platform.select<ColorValue>({
|
|
28
|
+
ios: PlatformColor("systemGray"),
|
|
29
|
+
default: "#707070",
|
|
30
|
+
}),
|
|
31
|
+
background: Platform.select({
|
|
32
|
+
ios: "#555756",
|
|
33
|
+
default: "#2C2C2E",
|
|
34
|
+
}),
|
|
35
|
+
ripple: "#F8F8F888",
|
|
36
|
+
},
|
|
37
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { KeyboardToolbarTheme } from "./types";
|
|
2
|
+
|
|
3
|
+
export const colors: KeyboardToolbarTheme = {
|
|
4
|
+
light: {
|
|
5
|
+
primary: "#2c2c2c",
|
|
6
|
+
disabled: "#B0BEC5",
|
|
7
|
+
background: "#f3f3f4",
|
|
8
|
+
ripple: "#bcbcbcbc",
|
|
9
|
+
},
|
|
10
|
+
dark: {
|
|
11
|
+
primary: "#fafafa",
|
|
12
|
+
disabled: "#707070",
|
|
13
|
+
background: "#2C2C2E",
|
|
14
|
+
ripple: "#F8F8F888",
|
|
15
|
+
},
|
|
16
|
+
};
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import React, { useEffect, useMemo, useState } from "react";
|
|
2
|
+
import { StyleSheet, Text, View } from "react-native";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
FocusedInputEvents,
|
|
6
|
+
KeyboardStickyView,
|
|
7
|
+
} from "react-native-keyboard-controller";
|
|
8
|
+
|
|
9
|
+
import { KeyboardController } from "../../bindings";
|
|
10
|
+
import useColorScheme from "../hooks/useColorScheme";
|
|
11
|
+
|
|
12
|
+
import Arrow from "./Arrow";
|
|
13
|
+
import Button from "./Button";
|
|
14
|
+
import { colors } from "./colors";
|
|
15
|
+
|
|
16
|
+
import type { KeyboardToolbarTheme } from "./types";
|
|
17
|
+
import type { ReactNode } from "react";
|
|
18
|
+
|
|
19
|
+
export type KeyboardToolbarProps = {
|
|
20
|
+
/** An element that is shown in the middle of the toolbar. */
|
|
21
|
+
content?: JSX.Element | null;
|
|
22
|
+
/** A set of dark/light colors consumed by toolbar component. */
|
|
23
|
+
theme?: KeyboardToolbarTheme;
|
|
24
|
+
/** Custom text for done button. */
|
|
25
|
+
doneText?: ReactNode;
|
|
26
|
+
/** Custom touchable component for toolbar (used for prev/next/done buttons). */
|
|
27
|
+
button?: typeof Button;
|
|
28
|
+
/** Custom icon component used to display next/prev buttons. */
|
|
29
|
+
icon?: typeof Arrow;
|
|
30
|
+
};
|
|
31
|
+
const TEST_ID_KEYBOARD_TOOLBAR = "keyboard.toolbar";
|
|
32
|
+
const TEST_ID_KEYBOARD_TOOLBAR_PREVIOUS = `${TEST_ID_KEYBOARD_TOOLBAR}.previous`;
|
|
33
|
+
const TEST_ID_KEYBOARD_TOOLBAR_NEXT = `${TEST_ID_KEYBOARD_TOOLBAR}.next`;
|
|
34
|
+
const TEST_ID_KEYBOARD_TOOLBAR_CONTENT = `${TEST_ID_KEYBOARD_TOOLBAR}.content`;
|
|
35
|
+
const TEST_ID_KEYBOARD_TOOLBAR_DONE = `${TEST_ID_KEYBOARD_TOOLBAR}.done`;
|
|
36
|
+
|
|
37
|
+
const KEYBOARD_TOOLBAR_HEIGHT = 42;
|
|
38
|
+
const offset = { closed: KEYBOARD_TOOLBAR_HEIGHT };
|
|
39
|
+
|
|
40
|
+
const dismissKeyboard = () => KeyboardController.dismiss();
|
|
41
|
+
const goToNextField = () => KeyboardController.setFocusTo("next");
|
|
42
|
+
const goToPrevField = () => KeyboardController.setFocusTo("prev");
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* `KeyboardToolbar` is a component that is shown above the keyboard with `Prev`/`Next` and
|
|
46
|
+
* `Done` buttons.
|
|
47
|
+
*/
|
|
48
|
+
const KeyboardToolbar: React.FC<KeyboardToolbarProps> = ({
|
|
49
|
+
content,
|
|
50
|
+
theme = colors,
|
|
51
|
+
doneText,
|
|
52
|
+
button,
|
|
53
|
+
icon,
|
|
54
|
+
}) => {
|
|
55
|
+
const colorScheme = useColorScheme();
|
|
56
|
+
const [inputs, setInputs] = useState({
|
|
57
|
+
current: 0,
|
|
58
|
+
count: 0,
|
|
59
|
+
});
|
|
60
|
+
const isPrevDisabled = inputs.current === 0;
|
|
61
|
+
const isNextDisabled = inputs.current === inputs.count - 1;
|
|
62
|
+
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
const subscription = FocusedInputEvents.addListener("focusDidSet", (e) => {
|
|
65
|
+
setInputs(e);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return subscription.remove;
|
|
69
|
+
}, []);
|
|
70
|
+
const doneStyle = useMemo(
|
|
71
|
+
() => [styles.doneButton, { color: theme[colorScheme].primary }],
|
|
72
|
+
[colorScheme, theme],
|
|
73
|
+
);
|
|
74
|
+
const toolbarStyle = useMemo(
|
|
75
|
+
() => [
|
|
76
|
+
styles.toolbar,
|
|
77
|
+
{
|
|
78
|
+
backgroundColor: theme[colorScheme].background,
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
[colorScheme, theme],
|
|
82
|
+
);
|
|
83
|
+
const ButtonContainer = button || Button;
|
|
84
|
+
const IconContainer = icon || Arrow;
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<KeyboardStickyView offset={offset}>
|
|
88
|
+
<View style={toolbarStyle} testID={TEST_ID_KEYBOARD_TOOLBAR}>
|
|
89
|
+
<ButtonContainer
|
|
90
|
+
accessibilityLabel="Previous"
|
|
91
|
+
accessibilityHint="Will move focus to previous field"
|
|
92
|
+
disabled={isPrevDisabled}
|
|
93
|
+
onPress={goToPrevField}
|
|
94
|
+
testID={TEST_ID_KEYBOARD_TOOLBAR_PREVIOUS}
|
|
95
|
+
theme={theme}
|
|
96
|
+
>
|
|
97
|
+
<IconContainer disabled={isPrevDisabled} type="prev" theme={theme} />
|
|
98
|
+
</ButtonContainer>
|
|
99
|
+
<ButtonContainer
|
|
100
|
+
accessibilityLabel="Next"
|
|
101
|
+
accessibilityHint="Will move focus to next field"
|
|
102
|
+
disabled={isNextDisabled}
|
|
103
|
+
onPress={goToNextField}
|
|
104
|
+
testID={TEST_ID_KEYBOARD_TOOLBAR_NEXT}
|
|
105
|
+
theme={theme}
|
|
106
|
+
>
|
|
107
|
+
<IconContainer disabled={isNextDisabled} type="next" theme={theme} />
|
|
108
|
+
</ButtonContainer>
|
|
109
|
+
|
|
110
|
+
<View style={styles.flex} testID={TEST_ID_KEYBOARD_TOOLBAR_CONTENT}>
|
|
111
|
+
{content}
|
|
112
|
+
</View>
|
|
113
|
+
<ButtonContainer
|
|
114
|
+
accessibilityLabel="Done"
|
|
115
|
+
accessibilityHint="Will close the keyboard"
|
|
116
|
+
onPress={dismissKeyboard}
|
|
117
|
+
testID={TEST_ID_KEYBOARD_TOOLBAR_DONE}
|
|
118
|
+
rippleRadius={28}
|
|
119
|
+
style={styles.doneButtonContainer}
|
|
120
|
+
theme={theme}
|
|
121
|
+
>
|
|
122
|
+
<Text style={doneStyle} maxFontSizeMultiplier={1.3}>
|
|
123
|
+
{doneText || "Done"}
|
|
124
|
+
</Text>
|
|
125
|
+
</ButtonContainer>
|
|
126
|
+
</View>
|
|
127
|
+
</KeyboardStickyView>
|
|
128
|
+
);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const styles = StyleSheet.create({
|
|
132
|
+
flex: {
|
|
133
|
+
flex: 1,
|
|
134
|
+
},
|
|
135
|
+
toolbar: {
|
|
136
|
+
position: "absolute",
|
|
137
|
+
bottom: 0,
|
|
138
|
+
alignItems: "center",
|
|
139
|
+
width: "100%",
|
|
140
|
+
flexDirection: "row",
|
|
141
|
+
height: KEYBOARD_TOOLBAR_HEIGHT,
|
|
142
|
+
paddingHorizontal: 8,
|
|
143
|
+
},
|
|
144
|
+
doneButton: {
|
|
145
|
+
fontWeight: "600",
|
|
146
|
+
fontSize: 15,
|
|
147
|
+
},
|
|
148
|
+
doneButtonContainer: {
|
|
149
|
+
marginRight: 8,
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
export { colors as DefaultKeyboardToolbarTheme };
|
|
154
|
+
export default KeyboardToolbar;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ColorValue } from "react-native";
|
|
2
|
+
|
|
3
|
+
type Theme = {
|
|
4
|
+
/** Color for arrow when it's enabled */
|
|
5
|
+
primary: ColorValue;
|
|
6
|
+
/** Color for arrow when it's disabled */
|
|
7
|
+
disabled: ColorValue;
|
|
8
|
+
/** Keyboard toolbar background color */
|
|
9
|
+
background: ColorValue;
|
|
10
|
+
/** Color for ripple effect (on button touch) on Android */
|
|
11
|
+
ripple: ColorValue;
|
|
12
|
+
};
|
|
13
|
+
export type KeyboardToolbarTheme = {
|
|
14
|
+
light: Theme;
|
|
15
|
+
dark: Theme;
|
|
16
|
+
};
|
|
@@ -49,15 +49,16 @@ const useKeyboardInterpolation = () => {
|
|
|
49
49
|
);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
const interpolation = interpolateREA(
|
|
53
53
|
keyboardPosition,
|
|
54
54
|
[prevKeyboardHeight.value, nextKeyboardHeight.value],
|
|
55
55
|
shouldUseInternalInterpolation.value
|
|
56
56
|
? [lastInterpolation.value, output[1]]
|
|
57
57
|
: output,
|
|
58
58
|
);
|
|
59
|
+
lastInterpolation.value = interpolation;
|
|
59
60
|
|
|
60
|
-
return
|
|
61
|
+
return interpolation;
|
|
61
62
|
};
|
|
62
63
|
|
|
63
64
|
useKeyboardHandler(
|
package/src/components/index.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
export { default as KeyboardAvoidingView } from "./KeyboardAvoidingView";
|
|
2
2
|
export { default as KeyboardStickyView } from "./KeyboardStickyView";
|
|
3
3
|
export { default as KeyboardAwareScrollView } from "./KeyboardAwareScrollView";
|
|
4
|
+
export {
|
|
5
|
+
default as KeyboardToolbar,
|
|
6
|
+
DefaultKeyboardToolbarTheme,
|
|
7
|
+
KeyboardToolbarProps,
|
|
8
|
+
} from "./KeyboardToolbar";
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export * from "./bindings";
|
|
2
2
|
export * from "./animated";
|
|
3
|
-
export * from "./replicas";
|
|
4
3
|
export * from "./context";
|
|
5
4
|
export * from "./hooks";
|
|
6
5
|
export * from "./constants";
|
|
@@ -10,4 +9,8 @@ export {
|
|
|
10
9
|
KeyboardAvoidingView,
|
|
11
10
|
KeyboardStickyView,
|
|
12
11
|
KeyboardAwareScrollView,
|
|
12
|
+
// keyboard toolbar
|
|
13
|
+
KeyboardToolbar,
|
|
14
|
+
DefaultKeyboardToolbarTheme,
|
|
15
|
+
KeyboardToolbarProps,
|
|
13
16
|
} from "./components";
|
package/src/types.ts
CHANGED
|
@@ -13,6 +13,7 @@ export type NativeEvent = {
|
|
|
13
13
|
};
|
|
14
14
|
export type FocusedInputLayoutChangedEvent = {
|
|
15
15
|
target: number;
|
|
16
|
+
parentScrollViewTarget: number;
|
|
16
17
|
layout: {
|
|
17
18
|
x: number;
|
|
18
19
|
y: number;
|
|
@@ -81,12 +82,14 @@ export type KeyboardGestureAreaProps = {
|
|
|
81
82
|
enableSwipeToDismiss?: boolean;
|
|
82
83
|
} & ViewProps;
|
|
83
84
|
|
|
85
|
+
export type Direction = "next" | "prev" | "current";
|
|
84
86
|
export type KeyboardControllerModule = {
|
|
85
87
|
// android only
|
|
86
88
|
setDefaultMode: () => void;
|
|
87
89
|
setInputMode: (mode: number) => void;
|
|
88
90
|
// all platforms
|
|
89
91
|
dismiss: () => void;
|
|
92
|
+
setFocusTo: (direction: Direction) => void;
|
|
90
93
|
// native event module stuff
|
|
91
94
|
addListener: (eventName: string) => void;
|
|
92
95
|
removeListeners: (count: number) => void;
|
|
@@ -110,6 +113,17 @@ export type KeyboardEventsModule = {
|
|
|
110
113
|
cb: (e: KeyboardEventData) => void,
|
|
111
114
|
) => EmitterSubscription;
|
|
112
115
|
};
|
|
116
|
+
export type FocusedInputAvailableEvents = "focusDidSet";
|
|
117
|
+
export type FocusedInputEventData = {
|
|
118
|
+
current: number;
|
|
119
|
+
count: number;
|
|
120
|
+
};
|
|
121
|
+
export type FocusedInputEventsModule = {
|
|
122
|
+
addListener: (
|
|
123
|
+
name: FocusedInputAvailableEvents,
|
|
124
|
+
cb: (e: FocusedInputEventData) => void,
|
|
125
|
+
) => EmitterSubscription;
|
|
126
|
+
};
|
|
113
127
|
|
|
114
128
|
// reanimated hook declaration
|
|
115
129
|
export type KeyboardHandlerHook<TContext, Event> = (
|