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.
Files changed (109) hide show
  1. package/README.md +1 -0
  2. package/android/build.gradle +6 -0
  3. package/android/src/fabric/java/com/reactnativekeyboardcontroller/KeyboardControllerModule.kt +4 -0
  4. package/android/src/main/java/com/reactnativekeyboardcontroller/events/FocusedInputLayoutChangedEvent.kt +2 -0
  5. package/android/src/main/java/com/reactnativekeyboardcontroller/extensions/EditText.kt +22 -0
  6. package/android/src/main/java/com/reactnativekeyboardcontroller/extensions/ThemedReactContext.kt +9 -0
  7. package/android/src/main/java/com/reactnativekeyboardcontroller/listeners/FocusedInputObserver.kt +20 -0
  8. package/android/src/main/java/com/reactnativekeyboardcontroller/listeners/KeyboardAnimationCallback.kt +7 -13
  9. package/android/src/main/java/com/reactnativekeyboardcontroller/modules/KeyboardControllerModuleImpl.kt +15 -0
  10. package/android/src/main/java/com/reactnativekeyboardcontroller/traversal/FocusedInputHolder.kt +16 -0
  11. package/android/src/main/java/com/reactnativekeyboardcontroller/traversal/ViewHierarchyNavigator.kt +101 -0
  12. package/android/src/paper/java/com/reactnativekeyboardcontroller/KeyboardControllerModule.kt +5 -0
  13. package/android/src/test/java/com/reactnativekeyboardcontroller/traversal/ViewHierarchyNavigatorTest.kt +188 -0
  14. package/ios/.swiftlint.yml +4 -0
  15. package/ios/Extensions.swift +29 -0
  16. package/ios/KeyboardController.xcodeproj/project.pbxproj +20 -0
  17. package/ios/KeyboardControllerModule.mm +18 -0
  18. package/ios/events/FocusedInputLayoutChangedEvent.m +3 -0
  19. package/ios/observers/FocusedInputObserver.swift +20 -4
  20. package/ios/observers/KeyboardMovementObserver.swift +5 -4
  21. package/ios/traversal/FocusedInputHolder.swift +32 -0
  22. package/ios/traversal/TextInput.swift +26 -0
  23. package/ios/traversal/ViewHierarchyNavigator.swift +116 -0
  24. package/ios/views/KeyboardControllerView.mm +5 -0
  25. package/ios/views/KeyboardControllerViewManager.swift +4 -1
  26. package/jest/index.js +20 -0
  27. package/lib/commonjs/bindings.js +12 -1
  28. package/lib/commonjs/bindings.js.map +1 -1
  29. package/lib/commonjs/bindings.native.js +11 -2
  30. package/lib/commonjs/bindings.native.js.map +1 -1
  31. package/lib/commonjs/components/KeyboardAwareScrollView/index.js +18 -6
  32. package/lib/commonjs/components/KeyboardAwareScrollView/index.js.map +1 -1
  33. package/lib/commonjs/components/KeyboardToolbar/Arrow.js +79 -0
  34. package/lib/commonjs/components/KeyboardToolbar/Arrow.js.map +1 -0
  35. package/lib/commonjs/components/KeyboardToolbar/Button.js +74 -0
  36. package/lib/commonjs/components/KeyboardToolbar/Button.js.map +1 -0
  37. package/lib/commonjs/components/KeyboardToolbar/colors.js +41 -0
  38. package/lib/commonjs/components/KeyboardToolbar/colors.js.map +1 -0
  39. package/lib/commonjs/components/KeyboardToolbar/index.js +136 -0
  40. package/lib/commonjs/components/KeyboardToolbar/index.js.map +1 -0
  41. package/lib/commonjs/components/hooks/useColorScheme.js +11 -0
  42. package/lib/commonjs/components/hooks/useColorScheme.js.map +1 -0
  43. package/lib/commonjs/components/hooks/useKeyboardInterpolation.js +3 -2
  44. package/lib/commonjs/components/hooks/useKeyboardInterpolation.js.map +1 -1
  45. package/lib/commonjs/components/index.js +21 -0
  46. package/lib/commonjs/components/index.js.map +1 -1
  47. package/lib/commonjs/index.js +22 -13
  48. package/lib/commonjs/index.js.map +1 -1
  49. package/lib/commonjs/specs/KeyboardControllerViewNativeComponent.js.map +1 -1
  50. package/lib/commonjs/specs/NativeKeyboardController.js.map +1 -1
  51. package/lib/commonjs/types.js.map +1 -1
  52. package/lib/module/bindings.js +10 -0
  53. package/lib/module/bindings.js.map +1 -1
  54. package/lib/module/bindings.native.js +9 -1
  55. package/lib/module/bindings.native.js.map +1 -1
  56. package/lib/module/components/KeyboardAwareScrollView/index.js +18 -6
  57. package/lib/module/components/KeyboardAwareScrollView/index.js.map +1 -1
  58. package/lib/module/components/KeyboardToolbar/Arrow.js +69 -0
  59. package/lib/module/components/KeyboardToolbar/Arrow.js.map +1 -0
  60. package/lib/module/components/KeyboardToolbar/Button.js +64 -0
  61. package/lib/module/components/KeyboardToolbar/Button.js.map +1 -0
  62. package/lib/module/components/KeyboardToolbar/colors.js +34 -0
  63. package/lib/module/components/KeyboardToolbar/colors.js.map +1 -0
  64. package/lib/module/components/KeyboardToolbar/index.js +121 -0
  65. package/lib/module/components/KeyboardToolbar/index.js.map +1 -0
  66. package/lib/module/components/hooks/useColorScheme.js +4 -0
  67. package/lib/module/components/hooks/useColorScheme.js.map +1 -0
  68. package/lib/module/components/hooks/useKeyboardInterpolation.js +3 -2
  69. package/lib/module/components/hooks/useKeyboardInterpolation.js.map +1 -1
  70. package/lib/module/components/index.js +1 -0
  71. package/lib/module/components/index.js.map +1 -1
  72. package/lib/module/index.js +3 -2
  73. package/lib/module/index.js.map +1 -1
  74. package/lib/module/specs/KeyboardControllerViewNativeComponent.js.map +1 -1
  75. package/lib/module/specs/NativeKeyboardController.js.map +1 -1
  76. package/lib/module/types.js.map +1 -1
  77. package/lib/typescript/bindings.d.ts +6 -1
  78. package/lib/typescript/bindings.native.d.ts +6 -1
  79. package/lib/typescript/components/KeyboardToolbar/Arrow.d.ts +9 -0
  80. package/lib/typescript/components/KeyboardToolbar/Button.d.ts +15 -0
  81. package/lib/typescript/components/KeyboardToolbar/colors.d.ts +17 -0
  82. package/lib/typescript/components/KeyboardToolbar/index.d.ts +25 -0
  83. package/lib/typescript/components/hooks/useColorScheme.d.ts +2 -0
  84. package/lib/typescript/components/index.d.ts +1 -0
  85. package/lib/typescript/index.d.ts +1 -2
  86. package/lib/typescript/specs/KeyboardControllerViewNativeComponent.d.ts +1 -0
  87. package/lib/typescript/specs/NativeKeyboardController.d.ts +1 -0
  88. package/lib/typescript/types.d.ts +11 -0
  89. package/package.json +5 -1
  90. package/src/bindings.native.ts +11 -1
  91. package/src/bindings.ts +9 -0
  92. package/src/components/KeyboardAwareScrollView/index.tsx +22 -2
  93. package/src/components/KeyboardToolbar/Arrow.tsx +80 -0
  94. package/src/components/KeyboardToolbar/Button.tsx +98 -0
  95. package/src/components/KeyboardToolbar/colors.ts +51 -0
  96. package/src/components/KeyboardToolbar/index.tsx +154 -0
  97. package/src/components/hooks/useColorScheme.ts +5 -0
  98. package/src/components/hooks/useKeyboardInterpolation.ts +3 -2
  99. package/src/components/index.ts +5 -0
  100. package/src/index.ts +4 -1
  101. package/src/specs/KeyboardControllerViewNativeComponent.ts +1 -0
  102. package/src/specs/NativeKeyboardController.ts +1 -0
  103. package/src/types.ts +14 -0
  104. package/lib/commonjs/replicas.js +0 -125
  105. package/lib/commonjs/replicas.js.map +0 -1
  106. package/lib/module/replicas.js +0 -115
  107. package/lib/module/replicas.js.map +0 -1
  108. package/lib/typescript/replicas.d.ts +0 -38
  109. 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 { ScrollView, ScrollViewProps } from "react-native";
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 `onScrollReanimated` is a fake prop needed for reanimated to intercept scroll events
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;
@@ -0,0 +1,5 @@
1
+ import { useColorScheme as useColorSchemeRN } from "react-native";
2
+
3
+ const useColorScheme = () => useColorSchemeRN() || "light";
4
+
5
+ export default useColorScheme;
@@ -49,15 +49,16 @@ const useKeyboardInterpolation = () => {
49
49
  );
50
50
  }
51
51
 
52
- lastInterpolation.value = interpolateREA(
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 lastInterpolation.value;
61
+ return interpolation;
61
62
  };
62
63
 
63
64
  useKeyboardHandler(
@@ -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";
@@ -17,6 +17,7 @@ type KeyboardMoveEvent = Readonly<{
17
17
 
18
18
  type FocusedInputLayoutChangedEvent = Readonly<{
19
19
  target: Int32;
20
+ parentScrollViewTarget: Int32;
20
21
  layout: {
21
22
  x: Double;
22
23
  y: Double;
@@ -9,6 +9,7 @@ export interface Spec extends TurboModule {
9
9
  setInputMode(mode: number): void;
10
10
  setDefaultMode(): void;
11
11
  dismiss(): void;
12
+ setFocusTo(direction: string): void;
12
13
 
13
14
  // event emitter
14
15
  addListener: (eventName: string) => void;
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> = (
@@ -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