react-native-keyboard-controller 1.17.4 → 1.18.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 (185) hide show
  1. package/README.md +4 -1
  2. package/android/src/base/java/com/reactnativekeyboardcontroller/KeyboardControllerPackage.kt +6 -5
  3. package/android/src/fabric/java/com/reactnativekeyboardcontroller/KeyboardBackgroundViewManager.kt +19 -0
  4. package/android/src/fabric/java/com/reactnativekeyboardcontroller/KeyboardControllerModule.kt +4 -0
  5. package/android/src/fabric/java/com/reactnativekeyboardcontroller/KeyboardControllerViewManager.kt +3 -5
  6. package/android/src/fabric/java/com/reactnativekeyboardcontroller/KeyboardGestureAreaViewManager.kt +3 -5
  7. package/android/src/fabric/java/com/reactnativekeyboardcontroller/OverKeyboardViewManager.kt +3 -5
  8. package/android/src/fabric/java/com/reactnativekeyboardcontroller/StatusBarManagerCompatModule.kt +3 -1
  9. package/android/src/main/java/com/reactnativekeyboardcontroller/extensions/Context.kt +20 -0
  10. package/android/src/main/java/com/reactnativekeyboardcontroller/extensions/ThemedReactContext.kt +2 -12
  11. package/android/src/main/java/com/reactnativekeyboardcontroller/managers/KeyboardBackgroundViewManagerImpl.kt +13 -0
  12. package/android/src/main/java/com/reactnativekeyboardcontroller/managers/KeyboardControllerViewManagerImpl.kt +2 -6
  13. package/android/src/main/java/com/reactnativekeyboardcontroller/managers/KeyboardGestureAreaViewManagerImpl.kt +1 -5
  14. package/android/src/main/java/com/reactnativekeyboardcontroller/managers/OverKeyboardViewManagerImpl.kt +1 -5
  15. package/android/src/main/java/com/reactnativekeyboardcontroller/modules/KeyboardControllerModuleImpl.kt +4 -0
  16. package/android/src/main/java/com/reactnativekeyboardcontroller/modules/{StatusBarManagerCompatModuleImpl.kt → statusbar/StatusBarManagerCompatModuleImpl.kt} +31 -5
  17. package/android/src/main/java/com/reactnativekeyboardcontroller/modules/statusbar/StatusBarModuleProxy.kt +86 -0
  18. package/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt +9 -11
  19. package/android/src/main/java/com/reactnativekeyboardcontroller/views/background/KeyboardBackgroundViewGroup.kt +35 -0
  20. package/android/src/main/java/com/reactnativekeyboardcontroller/views/background/Skins.kt +91 -0
  21. package/android/src/main/jni/reactnativekeyboardcontroller.h +1 -0
  22. package/android/src/main/res/values/attrs.xml +7 -0
  23. package/android/src/main/res/values/colors.xml +35 -0
  24. package/android/src/main/res/values-v29/colors.xml +5 -0
  25. package/android/src/main/res/values-v30/colors.xml +17 -0
  26. package/android/src/main/res/values-v33/colors.xml +16 -0
  27. package/android/src/main/res/values-v34/colors.xml +18 -0
  28. package/android/src/main/res/values-v35/colors.xml +10 -0
  29. package/android/src/paper/java/com/reactnativekeyboardcontroller/KeyboardBackgroundViewManager.kt +15 -0
  30. package/android/src/paper/java/com/reactnativekeyboardcontroller/KeyboardControllerModule.kt +5 -0
  31. package/android/src/paper/java/com/reactnativekeyboardcontroller/KeyboardControllerViewManager.kt +2 -5
  32. package/android/src/paper/java/com/reactnativekeyboardcontroller/KeyboardGestureAreaViewManager.kt +2 -5
  33. package/android/src/paper/java/com/reactnativekeyboardcontroller/OverKeyboardViewManager.kt +2 -5
  34. package/android/src/paper/java/com/reactnativekeyboardcontroller/StatusBarManagerCompatModule.kt +3 -1
  35. package/android/src/turbo/java/com/reactnativekeyboardcontroller/KeyboardControllerPackage.kt +5 -5
  36. package/common/cpp/react/renderer/components/reactnativekeyboardcontroller/RNKCKeyboardBackgroundViewComponentDescriptor.h +27 -0
  37. package/common/cpp/react/renderer/components/reactnativekeyboardcontroller/RNKCKeyboardBackgroundViewShadowNode.cpp +14 -0
  38. package/common/cpp/react/renderer/components/reactnativekeyboardcontroller/RNKCKeyboardBackgroundViewShadowNode.h +30 -0
  39. package/common/cpp/react/renderer/components/reactnativekeyboardcontroller/RNKCKeyboardBackgroundViewState.h +28 -0
  40. package/common/cpp/react/renderer/components/reactnativekeyboardcontroller/RNKCKeyboardExtenderComponentDescriptor.h +27 -0
  41. package/common/cpp/react/renderer/components/reactnativekeyboardcontroller/RNKCKeyboardExtenderShadowNode.cpp +14 -0
  42. package/common/cpp/react/renderer/components/reactnativekeyboardcontroller/RNKCKeyboardExtenderShadowNode.h +30 -0
  43. package/common/cpp/react/renderer/components/reactnativekeyboardcontroller/RNKCKeyboardExtenderState.h +28 -0
  44. package/ios/KeyboardControllerModule.mm +9 -0
  45. package/ios/delegates/KCTextInputCompositeDelegate.swift +5 -5
  46. package/ios/events/KeyboardEventEmitterPayload.swift +1 -1
  47. package/ios/extensions/String.swift +12 -0
  48. package/ios/extensions/UIResponder.swift +27 -0
  49. package/ios/interactive/KeyboardAreaExtender.swift +5 -5
  50. package/ios/observers/FocusedInputObserver.swift +14 -5
  51. package/ios/observers/KeyboardMovementObserver.swift +24 -9
  52. package/ios/protocols/TextInput.swift +22 -0
  53. package/ios/swizzling/UIResponderSwizzle.swift +3 -0
  54. package/ios/traversal/KeyboardView.swift +6 -3
  55. package/ios/views/KeyboardBackgroundViewManager.h +29 -0
  56. package/ios/views/KeyboardBackgroundViewManager.mm +168 -0
  57. package/ios/views/KeyboardExtenderContainerView.swift +59 -0
  58. package/ios/views/KeyboardExtenderManager.h +29 -0
  59. package/ios/views/KeyboardExtenderManager.mm +287 -0
  60. package/jest/index.js +6 -1
  61. package/lib/commonjs/animated.js +7 -10
  62. package/lib/commonjs/animated.js.map +1 -1
  63. package/lib/commonjs/bindings.js +15 -1
  64. package/lib/commonjs/bindings.js.map +1 -1
  65. package/lib/commonjs/bindings.native.js +5 -1
  66. package/lib/commonjs/bindings.native.js.map +1 -1
  67. package/lib/commonjs/components/KeyboardAwareScrollView/index.js +49 -29
  68. package/lib/commonjs/components/KeyboardAwareScrollView/index.js.map +1 -1
  69. package/lib/commonjs/components/KeyboardToolbar/Arrow.js +2 -3
  70. package/lib/commonjs/components/KeyboardToolbar/Arrow.js.map +1 -1
  71. package/lib/commonjs/components/KeyboardToolbar/Button.js +2 -3
  72. package/lib/commonjs/components/KeyboardToolbar/Button.js.map +1 -1
  73. package/lib/commonjs/components/KeyboardToolbar/constants.js +17 -0
  74. package/lib/commonjs/components/KeyboardToolbar/constants.js.map +1 -0
  75. package/lib/commonjs/components/KeyboardToolbar/index.js +25 -22
  76. package/lib/commonjs/components/KeyboardToolbar/index.js.map +1 -1
  77. package/lib/commonjs/hooks/useKeyboardState/index.js +18 -8
  78. package/lib/commonjs/hooks/useKeyboardState/index.js.map +1 -1
  79. package/lib/commonjs/index.js +8 -1
  80. package/lib/commonjs/index.js.map +1 -1
  81. package/lib/commonjs/module.js +2 -1
  82. package/lib/commonjs/module.js.map +1 -1
  83. package/lib/commonjs/specs/KeyboardBackgroundViewNativeComponent.js +12 -0
  84. package/lib/commonjs/specs/KeyboardBackgroundViewNativeComponent.js.map +1 -0
  85. package/lib/commonjs/specs/KeyboardExtenderNativeComponent.js +13 -0
  86. package/lib/commonjs/specs/KeyboardExtenderNativeComponent.js.map +1 -0
  87. package/lib/commonjs/specs/NativeKeyboardController.js.map +1 -1
  88. package/lib/commonjs/types/module.js.map +1 -1
  89. package/lib/commonjs/types/views.js.map +1 -1
  90. package/lib/commonjs/views/KeyboardExtender/index.ios.js +43 -0
  91. package/lib/commonjs/views/KeyboardExtender/index.ios.js.map +1 -0
  92. package/lib/commonjs/views/KeyboardExtender/index.js +46 -0
  93. package/lib/commonjs/views/KeyboardExtender/index.js.map +1 -0
  94. package/lib/commonjs/views/index.js +7 -0
  95. package/lib/commonjs/views/index.js.map +1 -1
  96. package/lib/module/animated.js +8 -11
  97. package/lib/module/animated.js.map +1 -1
  98. package/lib/module/bindings.js +14 -0
  99. package/lib/module/bindings.js.map +1 -1
  100. package/lib/module/bindings.native.js +4 -0
  101. package/lib/module/bindings.native.js.map +1 -1
  102. package/lib/module/components/KeyboardAwareScrollView/index.js +50 -30
  103. package/lib/module/components/KeyboardAwareScrollView/index.js.map +1 -1
  104. package/lib/module/components/KeyboardToolbar/Arrow.js +2 -2
  105. package/lib/module/components/KeyboardToolbar/Arrow.js.map +1 -1
  106. package/lib/module/components/KeyboardToolbar/Button.js +2 -2
  107. package/lib/module/components/KeyboardToolbar/Button.js.map +1 -1
  108. package/lib/module/components/KeyboardToolbar/constants.js +11 -0
  109. package/lib/module/components/KeyboardToolbar/constants.js.map +1 -0
  110. package/lib/module/components/KeyboardToolbar/index.js +17 -14
  111. package/lib/module/components/KeyboardToolbar/index.js.map +1 -1
  112. package/lib/module/hooks/useKeyboardState/index.js +18 -6
  113. package/lib/module/hooks/useKeyboardState/index.js.map +1 -1
  114. package/lib/module/index.js +1 -1
  115. package/lib/module/index.js.map +1 -1
  116. package/lib/module/module.js +2 -1
  117. package/lib/module/module.js.map +1 -1
  118. package/lib/module/specs/KeyboardBackgroundViewNativeComponent.js +5 -0
  119. package/lib/module/specs/KeyboardBackgroundViewNativeComponent.js.map +1 -0
  120. package/lib/module/specs/KeyboardExtenderNativeComponent.js +6 -0
  121. package/lib/module/specs/KeyboardExtenderNativeComponent.js.map +1 -0
  122. package/lib/module/specs/NativeKeyboardController.js.map +1 -1
  123. package/lib/module/types/module.js.map +1 -1
  124. package/lib/module/types/views.js.map +1 -1
  125. package/lib/module/views/KeyboardExtender/index.ios.js +36 -0
  126. package/lib/module/views/KeyboardExtender/index.ios.js.map +1 -0
  127. package/lib/module/views/KeyboardExtender/index.js +39 -0
  128. package/lib/module/views/KeyboardExtender/index.js.map +1 -0
  129. package/lib/module/views/index.js +1 -0
  130. package/lib/module/views/index.js.map +1 -1
  131. package/lib/typescript/animated.d.ts +7 -0
  132. package/lib/typescript/bindings.d.ts +14 -1
  133. package/lib/typescript/bindings.native.d.ts +3 -1
  134. package/lib/typescript/components/KeyboardAwareScrollView/index.d.ts +2 -2
  135. package/lib/typescript/components/KeyboardToolbar/constants.d.ts +10 -0
  136. package/lib/typescript/hooks/useKeyboardState/index.d.ts +8 -2
  137. package/lib/typescript/index.d.ts +1 -1
  138. package/lib/typescript/specs/KeyboardBackgroundViewNativeComponent.d.ts +6 -0
  139. package/lib/typescript/specs/KeyboardExtenderNativeComponent.d.ts +7 -0
  140. package/lib/typescript/specs/NativeKeyboardController.d.ts +1 -0
  141. package/lib/typescript/types/module.d.ts +15 -3
  142. package/lib/typescript/types/views.d.ts +5 -0
  143. package/lib/typescript/views/KeyboardExtender/index.d.ts +19 -0
  144. package/lib/typescript/views/KeyboardExtender/index.ios.d.ts +19 -0
  145. package/lib/typescript/views/index.d.ts +1 -0
  146. package/package.json +7 -3
  147. package/react-native.config.js +1 -0
  148. package/src/animated.tsx +14 -9
  149. package/src/bindings.native.ts +8 -0
  150. package/src/bindings.ts +18 -0
  151. package/src/components/KeyboardAwareScrollView/index.tsx +72 -47
  152. package/src/components/KeyboardToolbar/Arrow.tsx +2 -2
  153. package/src/components/KeyboardToolbar/Button.tsx +2 -2
  154. package/src/components/KeyboardToolbar/constants.ts +15 -0
  155. package/src/components/KeyboardToolbar/index.tsx +45 -17
  156. package/src/hooks/useKeyboardState/index.ts +25 -6
  157. package/src/index.ts +1 -1
  158. package/src/module.ts +2 -1
  159. package/src/specs/KeyboardBackgroundViewNativeComponent.ts +10 -0
  160. package/src/specs/KeyboardExtenderNativeComponent.ts +13 -0
  161. package/src/specs/NativeKeyboardController.ts +1 -0
  162. package/src/types/module.ts +17 -3
  163. package/src/types/views.ts +5 -0
  164. package/src/views/KeyboardExtender/index.ios.tsx +35 -0
  165. package/src/views/KeyboardExtender/index.tsx +42 -0
  166. package/src/views/index.ts +1 -0
  167. package/ios/extensions/UIKeyboardAppearance.swift +0 -21
  168. package/lib/commonjs/components/hooks/useColorScheme.js +0 -10
  169. package/lib/commonjs/components/hooks/useColorScheme.js.map +0 -1
  170. package/lib/commonjs/monkey-patch.android.js +0 -53
  171. package/lib/commonjs/monkey-patch.android.js.map +0 -1
  172. package/lib/commonjs/monkey-patch.js +0 -11
  173. package/lib/commonjs/monkey-patch.js.map +0 -1
  174. package/lib/module/components/hooks/useColorScheme.js +0 -4
  175. package/lib/module/components/hooks/useColorScheme.js.map +0 -1
  176. package/lib/module/monkey-patch.android.js +0 -42
  177. package/lib/module/monkey-patch.android.js.map +0 -1
  178. package/lib/module/monkey-patch.js +0 -5
  179. package/lib/module/monkey-patch.js.map +0 -1
  180. package/lib/typescript/components/hooks/useColorScheme.d.ts +0 -2
  181. package/lib/typescript/monkey-patch.android.d.ts +0 -2
  182. package/lib/typescript/monkey-patch.d.ts +0 -2
  183. package/src/components/hooks/useColorScheme.ts +0 -5
  184. package/src/monkey-patch.android.ts +0 -41
  185. package/src/monkey-patch.ts +0 -5
@@ -0,0 +1,19 @@
1
+ import React from "react";
2
+ import type { KeyboardExtenderProps } from "../../types";
3
+ import type { PropsWithChildren } from "react";
4
+ /**
5
+ * A component that embeds its children into the keyboard thus enhancing keyboard functionality.
6
+ *
7
+ * @param props - Component props.
8
+ * @returns A view component that renders inside the keyboard above all system buttons.
9
+ * @example
10
+ * ```tsx
11
+ * <KeyboardExtender>
12
+ * <Button>10$</Button>
13
+ * <Button>20$</Button>
14
+ * <Button>50$</Button>
15
+ * </KeyboardExtender>
16
+ * ```
17
+ */
18
+ declare const KeyboardExtender: (props: PropsWithChildren<KeyboardExtenderProps>) => React.JSX.Element;
19
+ export default KeyboardExtender;
@@ -0,0 +1,19 @@
1
+ import React from "react";
2
+ import type { KeyboardExtenderProps } from "../../types";
3
+ import type { PropsWithChildren } from "react";
4
+ /**
5
+ * A component that embeds its children into the keyboard thus enhancing keyboard functionality.
6
+ *
7
+ * @param props - Component props.
8
+ * @returns A view component that renders inside the keyboard above all system buttons.
9
+ * @example
10
+ * ```tsx
11
+ * <KeyboardExtender>
12
+ * <Button>10$</Button>
13
+ * <Button>20$</Button>
14
+ * <Button>50$</Button>
15
+ * </KeyboardExtender>
16
+ * ```
17
+ */
18
+ declare const KeyboardExtender: (props: PropsWithChildren<KeyboardExtenderProps>) => React.JSX.Element;
19
+ export default KeyboardExtender;
@@ -1 +1,2 @@
1
1
  export { default as OverKeyboardView } from "./OverKeyboardView";
2
+ export { default as KeyboardExtender } from "./KeyboardExtender";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-keyboard-controller",
3
- "version": "1.17.4",
3
+ "version": "1.18.0",
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",
@@ -60,11 +60,13 @@
60
60
  "avoid keyboard",
61
61
  "sticky view",
62
62
  "over keyboard view",
63
+ "keyboard background view",
63
64
  "keyboard aware scroll view",
64
65
  "keyboard toolbar",
65
66
  "keyboard done button",
66
67
  "keyboard next button",
67
68
  "keyboard previous button",
69
+ "extend keyboard",
68
70
  "ios",
69
71
  "android"
70
72
  ],
@@ -79,7 +81,7 @@
79
81
  "registry": "https://registry.npmjs.org/"
80
82
  },
81
83
  "dependencies": {
82
- "react-native-is-edge-to-edge": "^1.1.6"
84
+ "react-native-is-edge-to-edge": "^1.2.1"
83
85
  },
84
86
  "devDependencies": {
85
87
  "@commitlint/config-conventional": "^11.0.0",
@@ -180,7 +182,9 @@
180
182
  "componentProvider": {
181
183
  "KeyboardControllerView": "KeyboardControllerView",
182
184
  "KeyboardGestureArea": "KeyboardGestureArea",
183
- "OverKeyboardView": "OverKeyboardView"
185
+ "OverKeyboardView": "OverKeyboardView",
186
+ "KeyboardBackgroundView": "KeyboardBackgroundView",
187
+ "KeyboardExtender": "KeyboardExtender"
184
188
  }
185
189
  }
186
190
  }
@@ -6,6 +6,7 @@ module.exports = {
6
6
  "KeyboardControllerViewComponentDescriptor",
7
7
  "KeyboardGestureAreaComponentDescriptor",
8
8
  "OverKeyboardViewComponentDescriptor",
9
+ "KeyboardBackgroundViewComponentDescriptor",
9
10
  ],
10
11
  cmakeListsPath: "../android/src/main/jni/CMakeLists.txt",
11
12
  },
package/src/animated.tsx CHANGED
@@ -1,5 +1,5 @@
1
1
  /* eslint react/jsx-sort-props: off */
2
- import React, { useLayoutEffect, useMemo, useRef, useState } from "react";
2
+ import React, { useEffect, useMemo, useRef, useState } from "react";
3
3
  import { Animated, Platform, StyleSheet } from "react-native";
4
4
  import {
5
5
  controlEdgeToEdgeValues,
@@ -11,7 +11,7 @@ import { KeyboardControllerView } from "./bindings";
11
11
  import { KeyboardContext } from "./context";
12
12
  import { focusedInputEventsMap, keyboardEventsMap } from "./event-mappings";
13
13
  import { useAnimatedValue, useEventHandlerRegistration } from "./internal";
14
- import { applyMonkeyPatch, revertMonkeyPatch } from "./monkey-patch";
14
+ import { KeyboardController } from "./module";
15
15
  import {
16
16
  useAnimatedKeyboardHandler,
17
17
  useFocusedInputLayoutHandler,
@@ -83,6 +83,13 @@ type KeyboardProviderProps = {
83
83
  * Defaults to `true`.
84
84
  */
85
85
  enabled?: boolean;
86
+ /**
87
+ * A boolean prop indicating whether to preload the keyboard to reduce time-to-interaction (TTI) on first input focus.
88
+ * Defaults to `true`.
89
+ *
90
+ * @platform ios
91
+ */
92
+ preload?: boolean;
86
93
  };
87
94
 
88
95
  // capture `Platform.OS` in separate variable to avoid deep workletization of entire RN package
@@ -110,6 +117,7 @@ export const KeyboardProvider = (props: KeyboardProviderProps) => {
110
117
  navigationBarTranslucent,
111
118
  preserveEdgeToEdge,
112
119
  enabled: initiallyEnabled = true,
120
+ preload = true,
113
121
  } = props;
114
122
  // ref
115
123
  const viewTagRef = useRef<React.Component<KeyboardControllerProps>>(null);
@@ -216,14 +224,11 @@ export const KeyboardProvider = (props: KeyboardProviderProps) => {
216
224
  [],
217
225
  );
218
226
 
219
- // layout effects
220
- useLayoutEffect(() => {
221
- if (enabled) {
222
- applyMonkeyPatch();
223
- } else {
224
- revertMonkeyPatch();
227
+ useEffect(() => {
228
+ if (preload) {
229
+ KeyboardController.preload();
225
230
  }
226
- }, [enabled]);
231
+ }, [preload]);
227
232
 
228
233
  if (__DEV__) {
229
234
  controlEdgeToEdgeValues({
@@ -2,9 +2,11 @@ import { NativeEventEmitter, Platform } from "react-native";
2
2
 
3
3
  import type {
4
4
  FocusedInputEventsModule,
5
+ KeyboardBackgroundViewProps,
5
6
  KeyboardControllerNativeModule,
6
7
  KeyboardControllerProps,
7
8
  KeyboardEventsModule,
9
+ KeyboardExtenderProps,
8
10
  KeyboardGestureAreaProps,
9
11
  OverKeyboardViewProps,
10
12
  WindowDimensionsEventsModule,
@@ -60,3 +62,9 @@ export const KeyboardGestureArea: React.FC<KeyboardGestureAreaProps> =
60
62
  : ({ children }: KeyboardGestureAreaProps) => children;
61
63
  export const RCTOverKeyboardView: React.FC<OverKeyboardViewProps> =
62
64
  require("./specs/OverKeyboardViewNativeComponent").default;
65
+ export const KeyboardBackgroundView: React.FC<KeyboardBackgroundViewProps> =
66
+ require("./specs/KeyboardBackgroundViewNativeComponent").default;
67
+ export const RCTKeyboardExtender: React.FC<KeyboardExtenderProps> =
68
+ Platform.OS === "ios"
69
+ ? require("./specs/KeyboardExtenderNativeComponent").default
70
+ : ({ children }: KeyboardExtenderProps) => children;
package/src/bindings.ts CHANGED
@@ -2,9 +2,11 @@ import { View } from "react-native";
2
2
 
3
3
  import type {
4
4
  FocusedInputEventsModule,
5
+ KeyboardBackgroundViewProps,
5
6
  KeyboardControllerNativeModule,
6
7
  KeyboardControllerProps,
7
8
  KeyboardEventsModule,
9
+ KeyboardExtenderProps,
8
10
  KeyboardGestureAreaProps,
9
11
  OverKeyboardViewProps,
10
12
  WindowDimensionsEventsModule,
@@ -16,6 +18,7 @@ const NOOP = () => {};
16
18
  export const KeyboardControllerNative: KeyboardControllerNativeModule = {
17
19
  setDefaultMode: NOOP,
18
20
  setInputMode: NOOP,
21
+ preload: NOOP,
19
22
  dismiss: NOOP,
20
23
  setFocusTo: NOOP,
21
24
  addListener: NOOP,
@@ -59,3 +62,18 @@ export const KeyboardGestureArea =
59
62
  View as unknown as React.FC<KeyboardGestureAreaProps>;
60
63
  export const RCTOverKeyboardView =
61
64
  View as unknown as React.FC<OverKeyboardViewProps>;
65
+ /**
66
+ * A view that matches keyboard background.
67
+ *
68
+ * @see {@link https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/keyboard-background-view|Documentation} page for more details.
69
+ */
70
+ export const KeyboardBackgroundView =
71
+ View as unknown as React.FC<KeyboardBackgroundViewProps>;
72
+ /**
73
+ * A container that will embed its children into the keyboard
74
+ * and will always show them above the keyboard.
75
+ *
76
+ * @see {@link https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/keyboard-extender|Documentation} page for more details.
77
+ */
78
+ export const RCTKeyboardExtender =
79
+ View as unknown as React.FC<KeyboardExtenderProps>;
@@ -1,5 +1,6 @@
1
1
  import React, { forwardRef, useCallback, useEffect, useMemo } from "react";
2
2
  import Reanimated, {
3
+ clamp,
3
4
  interpolate,
4
5
  runOnUI,
5
6
  scrollTo,
@@ -32,7 +33,7 @@ import type {
32
33
  } from "react-native-keyboard-controller";
33
34
 
34
35
  export type KeyboardAwareScrollViewProps = {
35
- /** The distance between keyboard and focused `TextInput` when keyboard is shown. Default is `0`. */
36
+ /** The distance between the keyboard and the caret inside a focused `TextInput` when the keyboard is shown. Default is `0`. */
36
37
  bottomOffset?: number;
37
38
  /** Prevents automatic scrolling of the `ScrollView` when the keyboard gets hidden, maintaining the current screen position. Default is `false`. */
38
39
  disableScrollOnKeyboardHide?: boolean;
@@ -127,6 +128,8 @@ const KeyboardAwareScrollView = forwardRef<
127
128
  const scrollBeforeKeyboardMovement = useSharedValue(0);
128
129
  const { input } = useReanimatedFocusedInput();
129
130
  const layout = useSharedValue<FocusedInputLayoutChangedEvent | null>(null);
131
+ const lastSelection =
132
+ useSharedValue<FocusedInputSelectionChangedEvent | null>(null);
130
133
 
131
134
  const { height } = useWindowDimensions();
132
135
 
@@ -191,9 +194,9 @@ const KeyboardAwareScrollView = forwardRef<
191
194
  return interpolatedScrollTo;
192
195
  }
193
196
 
194
- if (absoluteY < 0) {
195
- const positionOnScreen = visibleRect - inputHeight - bottomOffset;
196
- const topOfScreen = scrollPosition.value + absoluteY;
197
+ if (point < 0) {
198
+ const positionOnScreen = visibleRect - bottomOffset;
199
+ const topOfScreen = scrollPosition.value + point;
197
200
 
198
201
  scrollTo(
199
202
  scrollViewAnimatedRef,
@@ -222,65 +225,89 @@ const KeyboardAwareScrollView = forwardRef<
222
225
  [extraKeyboardSpace],
223
226
  );
224
227
 
225
- const scrollFromCurrentPosition = useCallback(
226
- (customHeight?: number) => {
227
- "worklet";
228
+ const updateLayoutFromSelection = useCallback(() => {
229
+ "worklet";
228
230
 
229
- const prevScrollPosition = scrollPosition.value;
230
- const prevLayout = layout.value;
231
+ const customHeight = lastSelection.value?.selection.end.y;
231
232
 
232
- if (!input.value?.layout) {
233
- return;
234
- }
233
+ if (!input.value?.layout || !customHeight) {
234
+ return false;
235
+ }
235
236
 
236
- // eslint-disable-next-line react-compiler/react-compiler
237
- layout.value = {
238
- ...input.value,
239
- layout: {
240
- ...input.value.layout,
241
- height: customHeight ?? input.value.layout.height,
242
- },
243
- };
244
- scrollPosition.value = position.value;
245
- maybeScroll(keyboardHeight.value, true);
246
- scrollPosition.value = prevScrollPosition;
247
- layout.value = prevLayout;
248
- },
249
- [maybeScroll],
250
- );
251
- const onChangeText = useCallback(() => {
237
+ layout.value = {
238
+ ...input.value,
239
+ layout: {
240
+ ...input.value.layout,
241
+ // when we have multiline input with limited amount of lines, then custom height can be very big
242
+ // so we clamp it to max input height
243
+ height: clamp(customHeight, 0, input.value.layout.height),
244
+ },
245
+ };
246
+
247
+ return true;
248
+ }, [input, lastSelection, layout]);
249
+ const scrollFromCurrentPosition = useCallback(() => {
252
250
  "worklet";
253
251
 
254
- // if typing a text caused layout shift, then we need to ignore this handler
255
- // because this event will be handled in `useAnimatedReaction` below
256
- if (layout.value?.layout.height !== input.value?.layout.height) {
252
+ const prevScrollPosition = scrollPosition.value;
253
+ const prevLayout = layout.value;
254
+
255
+ if (!updateLayoutFromSelection()) {
257
256
  return;
258
257
  }
259
258
 
259
+ // eslint-disable-next-line react-compiler/react-compiler
260
+ scrollPosition.value = position.value;
261
+ maybeScroll(keyboardHeight.value, true);
262
+ scrollPosition.value = prevScrollPosition;
263
+ layout.value = prevLayout;
264
+ }, [maybeScroll]);
265
+ const onChangeText = useCallback(() => {
266
+ "worklet";
267
+
268
+ console.debug("maybeScroll - onChangeText");
260
269
  scrollFromCurrentPosition();
261
270
  }, [scrollFromCurrentPosition]);
271
+ const onChangeTextHandler = useMemo(
272
+ () => debounce(onChangeText, 200),
273
+ [onChangeText],
274
+ );
262
275
  const onSelectionChange = useCallback(
263
276
  (e: FocusedInputSelectionChangedEvent) => {
264
277
  "worklet";
265
278
 
279
+ const lastTarget = lastSelection.value?.target;
280
+ const latestSelection = lastSelection.value?.selection;
281
+
282
+ lastSelection.value = e;
283
+
284
+ if (e.target !== lastTarget) {
285
+ // ignore this event, because "focus changed" event handled in `useSmoothKeyboardHandler`
286
+ return;
287
+ }
288
+ // caret in the end + end coordinates has been changed -> we moved to a new line
289
+ // so input may grow
290
+ if (
291
+ e.selection.end.position === e.selection.start.position &&
292
+ latestSelection?.end.y !== e.selection.end.y
293
+ ) {
294
+ return scrollFromCurrentPosition();
295
+ }
296
+ // selection has been changed
266
297
  if (e.selection.start.position !== e.selection.end.position) {
267
- scrollFromCurrentPosition(e.selection.end.y);
298
+ return scrollFromCurrentPosition();
268
299
  }
269
- },
270
- [scrollFromCurrentPosition],
271
- );
272
300
 
273
- const onChangeTextHandler = useMemo(
274
- () => debounce(onChangeText, 200),
275
- [onChangeText],
301
+ onChangeTextHandler();
302
+ },
303
+ [scrollFromCurrentPosition, onChangeTextHandler],
276
304
  );
277
305
 
278
306
  useFocusedInputHandler(
279
307
  {
280
- onChangeText: onChangeTextHandler,
281
308
  onSelectionChange: onSelectionChange,
282
309
  },
283
- [onChangeTextHandler, onSelectionChange],
310
+ [onSelectionChange],
284
311
  );
285
312
 
286
313
  useSmoothKeyboardHandler(
@@ -322,9 +349,8 @@ const KeyboardAwareScrollView = forwardRef<
322
349
  // focus was changed
323
350
  if (focusWasChanged) {
324
351
  tag.value = e.target;
325
-
326
352
  // save position of focused text input when keyboard starts to move
327
- layout.value = input.value;
353
+ updateLayoutFromSelection();
328
354
  // save current scroll position - when keyboard will hide we'll reuse
329
355
  // this value to achieve smooth hide effect
330
356
  scrollBeforeKeyboardMovement.value = position.value;
@@ -369,11 +395,10 @@ const KeyboardAwareScrollView = forwardRef<
369
395
  current?.target === previous?.target &&
370
396
  current?.layout.height !== previous?.layout.height
371
397
  ) {
372
- const prevLayout = layout.value;
373
-
374
- layout.value = input.value;
375
- scrollPosition.value += maybeScroll(keyboardHeight.value, true);
376
- layout.value = prevLayout;
398
+ // input has changed layout - let's check if we need to scroll
399
+ // may happen when you paste text, then onSelectionChange will be
400
+ // fired earlier than text actually changes its layout
401
+ scrollFromCurrentPosition();
377
402
  }
378
403
  },
379
404
  [],
@@ -1,7 +1,7 @@
1
1
  import React, { useMemo } from "react";
2
2
  import { Animated, StyleSheet, View } from "react-native";
3
3
 
4
- import useColorScheme from "../hooks/useColorScheme";
4
+ import { useKeyboardState } from "../../hooks";
5
5
 
6
6
  import type { KeyboardToolbarTheme } from "./types";
7
7
  import type { ViewStyle } from "react-native";
@@ -13,7 +13,7 @@ type ArrowProps = {
13
13
  };
14
14
 
15
15
  const ArrowComponent: React.FC<ArrowProps> = ({ type, disabled, theme }) => {
16
- const colorScheme = useColorScheme();
16
+ const colorScheme = useKeyboardState((state) => state.appearance);
17
17
 
18
18
  const color = useMemo(
19
19
  () => ({
@@ -6,7 +6,7 @@ import {
6
6
  View,
7
7
  } from "react-native";
8
8
 
9
- import useColorScheme from "../hooks/useColorScheme";
9
+ import { useKeyboardState } from "../../hooks";
10
10
 
11
11
  import type { KeyboardToolbarTheme } from "./types";
12
12
  import type { PropsWithChildren } from "react";
@@ -64,7 +64,7 @@ const ButtonAndroid = ({
64
64
  style,
65
65
  theme,
66
66
  }: PropsWithChildren<ButtonProps>) => {
67
- const colorScheme = useColorScheme();
67
+ const colorScheme = useKeyboardState((state) => state.appearance);
68
68
  const accessibilityState = useMemo(() => ({ disabled }), [disabled]);
69
69
  const ripple = useMemo(
70
70
  () =>
@@ -0,0 +1,15 @@
1
+ import { Platform } from "react-native";
2
+
3
+ import type { HEX } from "./types";
4
+
5
+ export const TEST_ID_KEYBOARD_TOOLBAR = "keyboard.toolbar";
6
+ export const TEST_ID_KEYBOARD_TOOLBAR_PREVIOUS = `${TEST_ID_KEYBOARD_TOOLBAR}.previous`;
7
+ export const TEST_ID_KEYBOARD_TOOLBAR_NEXT = `${TEST_ID_KEYBOARD_TOOLBAR}.next`;
8
+ export const TEST_ID_KEYBOARD_TOOLBAR_CONTENT = `${TEST_ID_KEYBOARD_TOOLBAR}.content`;
9
+ export const TEST_ID_KEYBOARD_TOOLBAR_DONE = `${TEST_ID_KEYBOARD_TOOLBAR}.done`;
10
+
11
+ export const KEYBOARD_TOOLBAR_HEIGHT = 42;
12
+ export const DEFAULT_OPACITY: HEX = "FF";
13
+ export const KEYBOARD_HAS_ROUNDED_CORNERS =
14
+ Platform.OS === "ios" && parseInt(Platform.Version, 10) >= 26;
15
+ export const OPENED_OFFSET = KEYBOARD_HAS_ROUNDED_CORNERS ? -11 : 0;
@@ -2,13 +2,24 @@ import React, { useCallback, useEffect, useMemo, useState } from "react";
2
2
  import { StyleSheet, Text, View } from "react-native";
3
3
 
4
4
  import { FocusedInputEvents } from "../../bindings";
5
+ import { useKeyboardState } from "../../hooks";
5
6
  import { KeyboardController } from "../../module";
6
- import useColorScheme from "../hooks/useColorScheme";
7
7
  import KeyboardStickyView from "../KeyboardStickyView";
8
8
 
9
9
  import Arrow from "./Arrow";
10
10
  import Button from "./Button";
11
11
  import { colors } from "./colors";
12
+ import {
13
+ DEFAULT_OPACITY,
14
+ KEYBOARD_HAS_ROUNDED_CORNERS,
15
+ KEYBOARD_TOOLBAR_HEIGHT,
16
+ OPENED_OFFSET,
17
+ TEST_ID_KEYBOARD_TOOLBAR,
18
+ TEST_ID_KEYBOARD_TOOLBAR_CONTENT,
19
+ TEST_ID_KEYBOARD_TOOLBAR_DONE,
20
+ TEST_ID_KEYBOARD_TOOLBAR_NEXT,
21
+ TEST_ID_KEYBOARD_TOOLBAR_PREVIOUS,
22
+ } from "./constants";
12
23
 
13
24
  import type { HEX, KeyboardToolbarTheme } from "./types";
14
25
  import type { KeyboardStickyViewProps } from "../KeyboardStickyView";
@@ -65,15 +76,6 @@ export type KeyboardToolbarProps = Omit<
65
76
  insets?: SafeAreaInsets;
66
77
  } & Pick<KeyboardStickyViewProps, "offset" | "enabled">;
67
78
 
68
- const TEST_ID_KEYBOARD_TOOLBAR = "keyboard.toolbar";
69
- const TEST_ID_KEYBOARD_TOOLBAR_PREVIOUS = `${TEST_ID_KEYBOARD_TOOLBAR}.previous`;
70
- const TEST_ID_KEYBOARD_TOOLBAR_NEXT = `${TEST_ID_KEYBOARD_TOOLBAR}.next`;
71
- const TEST_ID_KEYBOARD_TOOLBAR_CONTENT = `${TEST_ID_KEYBOARD_TOOLBAR}.content`;
72
- const TEST_ID_KEYBOARD_TOOLBAR_DONE = `${TEST_ID_KEYBOARD_TOOLBAR}.done`;
73
-
74
- const KEYBOARD_TOOLBAR_HEIGHT = 42;
75
- const DEFAULT_OPACITY: HEX = "FF";
76
-
77
79
  /**
78
80
  * `KeyboardToolbar` is a component that is shown above the keyboard with `Prev`/`Next` buttons from left and
79
81
  * `Done` button from the right (to dismiss the keyboard). Allows to add customizable content (yours UI elements) in the middle.
@@ -104,7 +106,7 @@ const KeyboardToolbar: React.FC<KeyboardToolbarProps> = (props) => {
104
106
  insets,
105
107
  ...rest
106
108
  } = props;
107
- const colorScheme = useColorScheme();
109
+ const colorScheme = useKeyboardState((state) => state.appearance);
108
110
  const [inputs, setInputs] = useState({
109
111
  current: 0,
110
112
  count: 0,
@@ -129,15 +131,32 @@ const KeyboardToolbar: React.FC<KeyboardToolbarProps> = (props) => {
129
131
  {
130
132
  backgroundColor: `${theme[colorScheme].background}${opacity}`,
131
133
  },
132
- {
133
- paddingLeft: insets?.left,
134
- paddingRight: insets?.right,
135
- },
134
+ !KEYBOARD_HAS_ROUNDED_CORNERS
135
+ ? {
136
+ paddingLeft: insets?.left,
137
+ paddingRight: insets?.right,
138
+ }
139
+ : null,
140
+ KEYBOARD_HAS_ROUNDED_CORNERS ? styles.floating : null,
136
141
  ],
137
142
  [colorScheme, opacity, theme, insets],
138
143
  );
144
+ const containerStyle = useMemo(
145
+ () => [
146
+ KEYBOARD_HAS_ROUNDED_CORNERS
147
+ ? {
148
+ marginLeft: (insets?.left ?? 0) + 16,
149
+ marginRight: (insets?.right ?? 0) + 16,
150
+ }
151
+ : null,
152
+ ],
153
+ [insets],
154
+ );
139
155
  const offset = useMemo(
140
- () => ({ closed: closed + KEYBOARD_TOOLBAR_HEIGHT, opened }),
156
+ () => ({
157
+ closed: closed + KEYBOARD_TOOLBAR_HEIGHT,
158
+ opened: opened + OPENED_OFFSET,
159
+ }),
141
160
  [closed, opened],
142
161
  );
143
162
  const ButtonContainer = button || Button;
@@ -175,7 +194,11 @@ const KeyboardToolbar: React.FC<KeyboardToolbarProps> = (props) => {
175
194
  );
176
195
 
177
196
  return (
178
- <KeyboardStickyView enabled={enabled} offset={offset}>
197
+ <KeyboardStickyView
198
+ enabled={enabled}
199
+ offset={offset}
200
+ style={containerStyle}
201
+ >
179
202
  <View {...rest} style={toolbarStyle} testID={TEST_ID_KEYBOARD_TOOLBAR}>
180
203
  {blur}
181
204
  {showArrows && (
@@ -258,6 +281,11 @@ const styles = StyleSheet.create({
258
281
  marginRight: 16,
259
282
  marginLeft: 8,
260
283
  },
284
+ floating: {
285
+ alignSelf: "center",
286
+ borderRadius: 20,
287
+ overflow: "hidden",
288
+ },
261
289
  });
262
290
 
263
291
  export { colors as DefaultKeyboardToolbarTheme };
@@ -12,17 +12,25 @@ const getLatestState = () => ({
12
12
  isVisible: KeyboardController.isVisible(),
13
13
  });
14
14
 
15
+ type KeyboardStateSelector<T> = (state: KeyboardState) => T;
16
+
17
+ const defaultSelector: KeyboardStateSelector<KeyboardState> = (state) => state;
18
+
15
19
  /**
16
20
  * React Hook that represents the current keyboard state on iOS and Android.
17
21
  * It tracks keyboard visibility, height, appearance, type and other properties.
18
22
  * This hook subscribes to keyboard events and updates the state reactively.
19
23
  *
24
+ * @template T - A type of the returned object from the `selector`.
25
+ * @param selector - A function that receives the current keyboard state and picks only necessary properties to avoid frequent re-renders.
20
26
  * @returns Object {@link KeyboardState|containing} keyboard state information.
21
27
  * @see {@link https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/hooks/keyboard/use-keyboard-state|Documentation} page for more details.
22
28
  * @example
23
29
  * ```tsx
24
30
  * function MyComponent() {
25
- * const { isVisible, height } = useKeyboardState();
31
+ * const isVisible = useKeyboardState((state) => state.isVisible);
32
+ * const height = useKeyboardState((state) => state.height);
33
+ *
26
34
  * return (
27
35
  * <View>
28
36
  * <Text>Keyboard is {isVisible ? 'visible' : 'hidden'}</Text>
@@ -32,27 +40,38 @@ const getLatestState = () => ({
32
40
  * }
33
41
  * ```
34
42
  */
35
- export const useKeyboardState = (): KeyboardState => {
36
- const [state, setState] = useState(getLatestState);
43
+ function useKeyboardState<T = KeyboardState>(
44
+ selector: KeyboardStateSelector<T> = defaultSelector as KeyboardStateSelector<T>,
45
+ ): T {
46
+ const [state, setState] = useState<T>(() => selector(getLatestState()));
37
47
 
38
48
  useEffect(() => {
39
49
  const subscriptions = EVENTS.map((event) =>
40
50
  KeyboardEvents.addListener(event, () =>
41
51
  // state will be updated by global listener first,
42
52
  // so we simply read it and don't derive data from the event
43
- setState(getLatestState),
53
+ setState(selector(getLatestState())),
44
54
  ),
45
55
  );
56
+ // update `appearance` prematurely
57
+ const willShowSubscription = KeyboardEvents.addListener(
58
+ "keyboardWillShow",
59
+ (e) =>
60
+ setState(selector({ ...getLatestState(), appearance: e.appearance })),
61
+ );
46
62
 
47
63
  // we might have missed an update between reading a value in render and
48
64
  // `addListener` in this handler, so we set it here. If there was
49
65
  // no change, React will filter out this update as a no-op.
50
- setState(getLatestState);
66
+ setState(selector(getLatestState()));
51
67
 
52
68
  return () => {
53
69
  subscriptions.forEach((subscription) => subscription.remove());
70
+ willShowSubscription.remove();
54
71
  };
55
72
  }, []);
56
73
 
57
74
  return state;
58
- };
75
+ }
76
+
77
+ export { useKeyboardState };
package/src/index.ts CHANGED
@@ -20,4 +20,4 @@ export type {
20
20
  KeyboardAwareScrollViewProps,
21
21
  KeyboardToolbarProps,
22
22
  } from "./components";
23
- export { OverKeyboardView } from "./views";
23
+ export { OverKeyboardView, KeyboardExtender } from "./views";
package/src/module.ts CHANGED
@@ -13,7 +13,7 @@ let lastState: KeyboardEventData = {
13
13
  timestamp: new Date().getTime(),
14
14
  target: -1,
15
15
  type: "default",
16
- appearance: "default",
16
+ appearance: "light",
17
17
  };
18
18
 
19
19
  KeyboardEvents.addListener("keyboardDidHide", (e) => {
@@ -54,6 +54,7 @@ export const KeyboardController: KeyboardControllerModule = {
54
54
  setDefaultMode: KeyboardControllerNative.setDefaultMode,
55
55
  setInputMode: KeyboardControllerNative.setInputMode,
56
56
  setFocusTo: KeyboardControllerNative.setFocusTo,
57
+ preload: KeyboardControllerNative.preload,
57
58
  dismiss,
58
59
  isVisible,
59
60
  state,