react-native-keyboard-controller 1.10.6 → 1.11.0
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 +41 -0
- package/lib/commonjs/components/KeyboardToolbar/colors.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/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 +34 -0
- package/lib/module/components/KeyboardToolbar/colors.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/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 +17 -0
- package/lib/typescript/components/KeyboardToolbar/index.d.ts +25 -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.ts +51 -0
- package/src/components/KeyboardToolbar/index.tsx +154 -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
|
@@ -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 "./colors";
|
|
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 "./colors";
|
|
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,51 @@
|
|
|
1
|
+
import { Platform, PlatformColor } from "react-native";
|
|
2
|
+
|
|
3
|
+
import type { ColorValue } from "react-native";
|
|
4
|
+
|
|
5
|
+
type Theme = {
|
|
6
|
+
/** Color for arrow when it's enabled */
|
|
7
|
+
primary: ColorValue;
|
|
8
|
+
/** Color for arrow when it's disabled */
|
|
9
|
+
disabled: ColorValue;
|
|
10
|
+
/** Keyboard toolbar background color */
|
|
11
|
+
background: ColorValue;
|
|
12
|
+
/** Color for ripple effect (on button touch) on Android */
|
|
13
|
+
ripple: ColorValue;
|
|
14
|
+
};
|
|
15
|
+
export type KeyboardToolbarTheme = {
|
|
16
|
+
light: Theme;
|
|
17
|
+
dark: Theme;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const colors: KeyboardToolbarTheme = {
|
|
21
|
+
light: {
|
|
22
|
+
primary: Platform.select<ColorValue>({
|
|
23
|
+
ios: PlatformColor("link"),
|
|
24
|
+
default: "#2c2c2c",
|
|
25
|
+
}),
|
|
26
|
+
disabled: Platform.select<ColorValue>({
|
|
27
|
+
ios: PlatformColor("systemGray4"),
|
|
28
|
+
default: "#B0BEC5",
|
|
29
|
+
}),
|
|
30
|
+
background: Platform.select({
|
|
31
|
+
ios: "#F8F8F8",
|
|
32
|
+
default: "#f3f3f4",
|
|
33
|
+
}),
|
|
34
|
+
ripple: "#bcbcbcbc",
|
|
35
|
+
},
|
|
36
|
+
dark: {
|
|
37
|
+
primary: Platform.select<ColorValue>({
|
|
38
|
+
ios: PlatformColor("label"),
|
|
39
|
+
default: "#fafafa",
|
|
40
|
+
}),
|
|
41
|
+
disabled: Platform.select<ColorValue>({
|
|
42
|
+
ios: PlatformColor("systemGray"),
|
|
43
|
+
default: "#707070",
|
|
44
|
+
}),
|
|
45
|
+
background: Platform.select({
|
|
46
|
+
ios: "#555756",
|
|
47
|
+
default: "#2C2C2E",
|
|
48
|
+
}),
|
|
49
|
+
ripple: "#F8F8F888",
|
|
50
|
+
},
|
|
51
|
+
};
|
|
@@ -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 "./colors";
|
|
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;
|
|
@@ -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> = (
|
package/lib/commonjs/replicas.js
DELETED
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.useReanimatedKeyboardAnimationReplica = exports.useKeyboardAnimationReplica = exports.useGradualKeyboardAnimation = exports.defaultAndroidEasing = void 0;
|
|
7
|
-
var _react = require("react");
|
|
8
|
-
var _reactNative = require("react-native");
|
|
9
|
-
var _reactNativeReanimated = require("react-native-reanimated");
|
|
10
|
-
var _bindings = require("./bindings");
|
|
11
|
-
var _constants = require("./constants");
|
|
12
|
-
var _hooks = require("./hooks");
|
|
13
|
-
const availableOSEventType = _reactNative.Platform.OS === "ios" ? "Will" : "Did";
|
|
14
|
-
|
|
15
|
-
// cubic-bezier(.17,.67,.34,.94)
|
|
16
|
-
const defaultAndroidEasing = _reactNative.Easing.bezier(0.4, 0.0, 0.2, 1);
|
|
17
|
-
exports.defaultAndroidEasing = defaultAndroidEasing;
|
|
18
|
-
/**
|
|
19
|
-
* An experimental implementation of tracing keyboard appearance.
|
|
20
|
-
* Switch an input mode to adjust resize mode. In this case all did* events
|
|
21
|
-
* are triggering before keyboard appears, and using some approximations
|
|
22
|
-
* it tries to mimicries a native transition.
|
|
23
|
-
*
|
|
24
|
-
* @returns {Animated.Value}
|
|
25
|
-
*/
|
|
26
|
-
const useKeyboardAnimationReplica = () => {
|
|
27
|
-
const height = (0, _react.useRef)(new _reactNative.Animated.Value(0));
|
|
28
|
-
const progress = (0, _react.useRef)(new _reactNative.Animated.Value(0));
|
|
29
|
-
const animation = (0, _react.useMemo)(() => ({
|
|
30
|
-
height: height.current,
|
|
31
|
-
progress: progress.current
|
|
32
|
-
}), []);
|
|
33
|
-
(0, _react.useEffect)(() => {
|
|
34
|
-
_bindings.KeyboardController.setInputMode(_constants.AndroidSoftInputModes.SOFT_INPUT_ADJUST_RESIZE);
|
|
35
|
-
return () => _bindings.KeyboardController.setDefaultMode();
|
|
36
|
-
}, []);
|
|
37
|
-
(0, _react.useEffect)(() => {
|
|
38
|
-
const listener = _reactNative.Keyboard.addListener(`keyboard${availableOSEventType}Show`, e => {
|
|
39
|
-
_reactNative.Animated.timing(height.current, {
|
|
40
|
-
toValue: -e.endCoordinates.height,
|
|
41
|
-
duration: e.duration !== 0 ? e.duration : 300,
|
|
42
|
-
easing: _reactNative.Easing.bezier(0.4, 0.0, 0.2, 1),
|
|
43
|
-
useNativeDriver: true
|
|
44
|
-
}).start();
|
|
45
|
-
return () => listener.remove();
|
|
46
|
-
});
|
|
47
|
-
}, []);
|
|
48
|
-
(0, _react.useEffect)(() => {
|
|
49
|
-
const listener = _reactNative.Keyboard.addListener(`keyboard${availableOSEventType}Hide`, e => {
|
|
50
|
-
_reactNative.Animated.timing(height.current, {
|
|
51
|
-
toValue: 0,
|
|
52
|
-
duration: e.duration !== 0 ? e.duration : 300,
|
|
53
|
-
easing: _reactNative.Easing.bezier(0.4, 0.0, 0.2, 1),
|
|
54
|
-
useNativeDriver: true
|
|
55
|
-
}).start();
|
|
56
|
-
return () => listener.remove();
|
|
57
|
-
});
|
|
58
|
-
}, []);
|
|
59
|
-
return animation;
|
|
60
|
-
};
|
|
61
|
-
exports.useKeyboardAnimationReplica = useKeyboardAnimationReplica;
|
|
62
|
-
const IOS_SPRING_CONFIG = {
|
|
63
|
-
damping: 500,
|
|
64
|
-
stiffness: 1000,
|
|
65
|
-
mass: 3,
|
|
66
|
-
overshootClamping: true,
|
|
67
|
-
restDisplacementThreshold: 10,
|
|
68
|
-
restSpeedThreshold: 10
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* A close replica to native iOS keyboard animation. The problem is that
|
|
73
|
-
* iOS (unlike Android) can not fire events for each keyboard frame movement.
|
|
74
|
-
* As a result we can not get gradual values (for example, for progress it always
|
|
75
|
-
* will be 1 or 0). So if you want to rely on gradual values you will need to use
|
|
76
|
-
* this replica.
|
|
77
|
-
*
|
|
78
|
-
* The transition is hardcoded and may vary from one to another OS versions. But it
|
|
79
|
-
* seems like last time it has been changed in iOS 7. Since RN supports at least iOS
|
|
80
|
-
* 11 it doesn't make sense to replicate iOS 7 behavior. If it changes in next OS
|
|
81
|
-
* versions, then this implementation should be revisited and reflect necessary changes.
|
|
82
|
-
*
|
|
83
|
-
* @returns {height, progress} - animated values
|
|
84
|
-
*/
|
|
85
|
-
const useReanimatedKeyboardAnimationReplica = () => {
|
|
86
|
-
const height = (0, _reactNativeReanimated.useSharedValue)(0);
|
|
87
|
-
const heightEvent = (0, _reactNativeReanimated.useSharedValue)(0);
|
|
88
|
-
const progress = (0, _reactNativeReanimated.useDerivedValue)(() => height.value / heightEvent.value);
|
|
89
|
-
const handler = (0, _react.useCallback)(_height => {
|
|
90
|
-
"worklet";
|
|
91
|
-
|
|
92
|
-
heightEvent.value = _height;
|
|
93
|
-
}, []);
|
|
94
|
-
(0, _reactNativeReanimated.useAnimatedReaction)(() => ({
|
|
95
|
-
_keyboardHeight: heightEvent.value
|
|
96
|
-
}), (result, _previousResult) => {
|
|
97
|
-
const {
|
|
98
|
-
_keyboardHeight
|
|
99
|
-
} = result;
|
|
100
|
-
const _previousKeyboardHeight = _previousResult === null || _previousResult === void 0 ? void 0 : _previousResult._keyboardHeight;
|
|
101
|
-
if (_keyboardHeight !== _previousKeyboardHeight) {
|
|
102
|
-
height.value = (0, _reactNativeReanimated.withSpring)(_keyboardHeight, IOS_SPRING_CONFIG);
|
|
103
|
-
}
|
|
104
|
-
}, []);
|
|
105
|
-
(0, _react.useEffect)(() => {
|
|
106
|
-
const show = _reactNative.Keyboard.addListener("keyboardWillShow", e => {
|
|
107
|
-
(0, _reactNativeReanimated.runOnUI)(handler)(-e.endCoordinates.height);
|
|
108
|
-
});
|
|
109
|
-
const hide = _reactNative.Keyboard.addListener("keyboardWillHide", () => {
|
|
110
|
-
(0, _reactNativeReanimated.runOnUI)(handler)(0);
|
|
111
|
-
});
|
|
112
|
-
return () => {
|
|
113
|
-
show.remove();
|
|
114
|
-
hide.remove();
|
|
115
|
-
};
|
|
116
|
-
}, []);
|
|
117
|
-
return {
|
|
118
|
-
height,
|
|
119
|
-
progress
|
|
120
|
-
};
|
|
121
|
-
};
|
|
122
|
-
exports.useReanimatedKeyboardAnimationReplica = useReanimatedKeyboardAnimationReplica;
|
|
123
|
-
const useGradualKeyboardAnimation = _reactNative.Platform.OS === "ios" ? useReanimatedKeyboardAnimationReplica : _hooks.useReanimatedKeyboardAnimation;
|
|
124
|
-
exports.useGradualKeyboardAnimation = useGradualKeyboardAnimation;
|
|
125
|
-
//# sourceMappingURL=replicas.js.map
|