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.
Files changed (121) 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 +22 -0
  38. package/lib/commonjs/components/KeyboardToolbar/colors.js.map +1 -0
  39. package/lib/commonjs/components/KeyboardToolbar/colors.native.js +41 -0
  40. package/lib/commonjs/components/KeyboardToolbar/colors.native.js.map +1 -0
  41. package/lib/commonjs/components/KeyboardToolbar/index.js +136 -0
  42. package/lib/commonjs/components/KeyboardToolbar/index.js.map +1 -0
  43. package/lib/commonjs/components/KeyboardToolbar/types.js +6 -0
  44. package/lib/commonjs/components/KeyboardToolbar/types.js.map +1 -0
  45. package/lib/commonjs/components/hooks/useColorScheme.js +11 -0
  46. package/lib/commonjs/components/hooks/useColorScheme.js.map +1 -0
  47. package/lib/commonjs/components/hooks/useKeyboardInterpolation.js +3 -2
  48. package/lib/commonjs/components/hooks/useKeyboardInterpolation.js.map +1 -1
  49. package/lib/commonjs/components/index.js +21 -0
  50. package/lib/commonjs/components/index.js.map +1 -1
  51. package/lib/commonjs/index.js +22 -13
  52. package/lib/commonjs/index.js.map +1 -1
  53. package/lib/commonjs/specs/KeyboardControllerViewNativeComponent.js.map +1 -1
  54. package/lib/commonjs/specs/NativeKeyboardController.js.map +1 -1
  55. package/lib/commonjs/types.js.map +1 -1
  56. package/lib/module/bindings.js +10 -0
  57. package/lib/module/bindings.js.map +1 -1
  58. package/lib/module/bindings.native.js +9 -1
  59. package/lib/module/bindings.native.js.map +1 -1
  60. package/lib/module/components/KeyboardAwareScrollView/index.js +18 -6
  61. package/lib/module/components/KeyboardAwareScrollView/index.js.map +1 -1
  62. package/lib/module/components/KeyboardToolbar/Arrow.js +69 -0
  63. package/lib/module/components/KeyboardToolbar/Arrow.js.map +1 -0
  64. package/lib/module/components/KeyboardToolbar/Button.js +64 -0
  65. package/lib/module/components/KeyboardToolbar/Button.js.map +1 -0
  66. package/lib/module/components/KeyboardToolbar/colors.js +15 -0
  67. package/lib/module/components/KeyboardToolbar/colors.js.map +1 -0
  68. package/lib/module/components/KeyboardToolbar/colors.native.js +34 -0
  69. package/lib/module/components/KeyboardToolbar/colors.native.js.map +1 -0
  70. package/lib/module/components/KeyboardToolbar/index.js +121 -0
  71. package/lib/module/components/KeyboardToolbar/index.js.map +1 -0
  72. package/lib/module/components/KeyboardToolbar/types.js +2 -0
  73. package/lib/module/components/KeyboardToolbar/types.js.map +1 -0
  74. package/lib/module/components/hooks/useColorScheme.js +4 -0
  75. package/lib/module/components/hooks/useColorScheme.js.map +1 -0
  76. package/lib/module/components/hooks/useKeyboardInterpolation.js +3 -2
  77. package/lib/module/components/hooks/useKeyboardInterpolation.js.map +1 -1
  78. package/lib/module/components/index.js +1 -0
  79. package/lib/module/components/index.js.map +1 -1
  80. package/lib/module/index.js +3 -2
  81. package/lib/module/index.js.map +1 -1
  82. package/lib/module/specs/KeyboardControllerViewNativeComponent.js.map +1 -1
  83. package/lib/module/specs/NativeKeyboardController.js.map +1 -1
  84. package/lib/module/types.js.map +1 -1
  85. package/lib/typescript/bindings.d.ts +6 -1
  86. package/lib/typescript/bindings.native.d.ts +6 -1
  87. package/lib/typescript/components/KeyboardToolbar/Arrow.d.ts +9 -0
  88. package/lib/typescript/components/KeyboardToolbar/Button.d.ts +15 -0
  89. package/lib/typescript/components/KeyboardToolbar/colors.d.ts +2 -0
  90. package/lib/typescript/components/KeyboardToolbar/colors.native.d.ts +2 -0
  91. package/lib/typescript/components/KeyboardToolbar/index.d.ts +25 -0
  92. package/lib/typescript/components/KeyboardToolbar/types.d.ts +16 -0
  93. package/lib/typescript/components/hooks/useColorScheme.d.ts +2 -0
  94. package/lib/typescript/components/index.d.ts +1 -0
  95. package/lib/typescript/index.d.ts +1 -2
  96. package/lib/typescript/specs/KeyboardControllerViewNativeComponent.d.ts +1 -0
  97. package/lib/typescript/specs/NativeKeyboardController.d.ts +1 -0
  98. package/lib/typescript/types.d.ts +11 -0
  99. package/package.json +5 -1
  100. package/src/bindings.native.ts +11 -1
  101. package/src/bindings.ts +9 -0
  102. package/src/components/KeyboardAwareScrollView/index.tsx +22 -2
  103. package/src/components/KeyboardToolbar/Arrow.tsx +80 -0
  104. package/src/components/KeyboardToolbar/Button.tsx +98 -0
  105. package/src/components/KeyboardToolbar/colors.native.ts +37 -0
  106. package/src/components/KeyboardToolbar/colors.ts +16 -0
  107. package/src/components/KeyboardToolbar/index.tsx +154 -0
  108. package/src/components/KeyboardToolbar/types.ts +16 -0
  109. package/src/components/hooks/useColorScheme.ts +5 -0
  110. package/src/components/hooks/useKeyboardInterpolation.ts +3 -2
  111. package/src/components/index.ts +5 -0
  112. package/src/index.ts +4 -1
  113. package/src/specs/KeyboardControllerViewNativeComponent.ts +1 -0
  114. package/src/specs/NativeKeyboardController.ts +1 -0
  115. package/src/types.ts +14 -0
  116. package/lib/commonjs/replicas.js +0 -125
  117. package/lib/commonjs/replicas.js.map +0 -1
  118. package/lib/module/replicas.js +0 -115
  119. package/lib/module/replicas.js.map +0 -1
  120. package/lib/typescript/replicas.d.ts +0 -38
  121. 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.10.6",
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
  ],
@@ -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("KeyboardController::" + name, cb),
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 { 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 "./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
+ };
@@ -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> = (