react-native-keyboard-controller 1.21.2 → 1.21.4

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 (54) hide show
  1. package/android/src/fabric/java/com/reactnativekeyboardcontroller/KeyboardControllerModule.kt +2 -0
  2. package/android/src/main/java/com/reactnativekeyboardcontroller/listeners/WindowDimensionListener.kt +1 -0
  3. package/android/src/main/java/com/reactnativekeyboardcontroller/managers/KeyboardControllerViewManagerImpl.kt +5 -1
  4. package/android/src/main/java/com/reactnativekeyboardcontroller/modules/KeyboardControllerModuleImpl.kt +8 -0
  5. package/android/src/main/java/com/reactnativekeyboardcontroller/views/ClippingScrollViewDecoratorView.kt +18 -1
  6. package/android/src/paper/java/com/reactnativekeyboardcontroller/KeyboardControllerModule.kt +2 -0
  7. package/ios/KeyboardControllerModule.mm +12 -0
  8. package/ios/observers/FocusedInputObserver.swift +9 -2
  9. package/ios/views/KeyboardExtenderContainerView.swift +15 -2
  10. package/lib/commonjs/bindings.js +4 -1
  11. package/lib/commonjs/bindings.js.map +1 -1
  12. package/lib/commonjs/components/KeyboardAwareScrollView/index.js +69 -17
  13. package/lib/commonjs/components/KeyboardAwareScrollView/index.js.map +1 -1
  14. package/lib/commonjs/components/KeyboardAwareScrollView/types.js +6 -0
  15. package/lib/commonjs/components/KeyboardAwareScrollView/types.js.map +1 -0
  16. package/lib/commonjs/components/KeyboardToolbar/constants.js +2 -2
  17. package/lib/commonjs/components/KeyboardToolbar/constants.js.map +1 -1
  18. package/lib/commonjs/components/index.js.map +1 -1
  19. package/lib/commonjs/constants.js +3 -1
  20. package/lib/commonjs/constants.js.map +1 -1
  21. package/lib/commonjs/index.js.map +1 -1
  22. package/lib/commonjs/specs/NativeKeyboardController.js.map +1 -1
  23. package/lib/commonjs/types/module.js.map +1 -1
  24. package/lib/module/bindings.js +4 -1
  25. package/lib/module/bindings.js.map +1 -1
  26. package/lib/module/components/KeyboardAwareScrollView/index.js +70 -18
  27. package/lib/module/components/KeyboardAwareScrollView/index.js.map +1 -1
  28. package/lib/module/components/KeyboardAwareScrollView/types.js +2 -0
  29. package/lib/module/components/KeyboardAwareScrollView/types.js.map +1 -0
  30. package/lib/module/components/KeyboardToolbar/constants.js +2 -2
  31. package/lib/module/components/KeyboardToolbar/constants.js.map +1 -1
  32. package/lib/module/components/index.js.map +1 -1
  33. package/lib/module/constants.js +3 -0
  34. package/lib/module/constants.js.map +1 -1
  35. package/lib/module/index.js.map +1 -1
  36. package/lib/module/specs/NativeKeyboardController.js.map +1 -1
  37. package/lib/module/types/module.js.map +1 -1
  38. package/lib/typescript/components/KeyboardAwareScrollView/index.d.ts +4 -24
  39. package/lib/typescript/components/KeyboardAwareScrollView/types.d.ts +27 -0
  40. package/lib/typescript/components/index.d.ts +1 -1
  41. package/lib/typescript/constants.d.ts +1 -0
  42. package/lib/typescript/index.d.ts +1 -1
  43. package/lib/typescript/specs/NativeKeyboardController.d.ts +3 -1
  44. package/lib/typescript/types/module.d.ts +3 -0
  45. package/package.json +1 -1
  46. package/src/bindings.ts +3 -0
  47. package/src/components/KeyboardAwareScrollView/index.tsx +106 -58
  48. package/src/components/KeyboardAwareScrollView/types.ts +29 -0
  49. package/src/components/KeyboardToolbar/constants.ts +2 -3
  50. package/src/components/index.ts +2 -1
  51. package/src/constants.ts +4 -0
  52. package/src/index.ts +1 -0
  53. package/src/specs/NativeKeyboardController.ts +3 -1
  54. package/src/types/module.ts +4 -0
@@ -1 +1 @@
1
- {"version":3,"names":["KeyboardChatScrollView","KeyboardAvoidingView","KeyboardStickyView","KeyboardAwareScrollView","KeyboardToolbar","DefaultKeyboardToolbarTheme","OverKeyboardView","KeyboardExtender"],"sources":["index.ts"],"sourcesContent":["export * from \"./bindings\";\nexport * from \"./animated\";\nexport * from \"./context\";\nexport * from \"./hooks\";\nexport * from \"./constants\";\nexport * from \"./module\";\nexport * from \"./types\";\nexport * from \"./compat\";\n\nexport {\n KeyboardChatScrollView,\n KeyboardAvoidingView,\n KeyboardStickyView,\n KeyboardAwareScrollView,\n // keyboard toolbar\n KeyboardToolbar,\n DefaultKeyboardToolbarTheme,\n} from \"./components\";\nexport type {\n KeyboardChatScrollViewProps,\n KeyboardAvoidingViewProps,\n KeyboardStickyViewProps,\n KeyboardAwareScrollViewProps,\n KeyboardAwareScrollViewRef,\n KeyboardToolbarProps,\n} from \"./components\";\nexport { OverKeyboardView, KeyboardExtender } from \"./views\";\n"],"mappings":"AAAA,cAAc,YAAY;AAC1B,cAAc,YAAY;AAC1B,cAAc,WAAW;AACzB,cAAc,SAAS;AACvB,cAAc,aAAa;AAC3B,cAAc,UAAU;AACxB,cAAc,SAAS;AACvB,cAAc,UAAU;AAExB,SACEA,sBAAsB,EACtBC,oBAAoB,EACpBC,kBAAkB,EAClBC,uBAAuB;AACvB;AACAC,eAAe,EACfC,2BAA2B,QACtB,cAAc;AASrB,SAASC,gBAAgB,EAAEC,gBAAgB,QAAQ,SAAS","ignoreList":[]}
1
+ {"version":3,"names":["KeyboardChatScrollView","KeyboardAvoidingView","KeyboardStickyView","KeyboardAwareScrollView","KeyboardToolbar","DefaultKeyboardToolbarTheme","OverKeyboardView","KeyboardExtender"],"sources":["index.ts"],"sourcesContent":["export * from \"./bindings\";\nexport * from \"./animated\";\nexport * from \"./context\";\nexport * from \"./hooks\";\nexport * from \"./constants\";\nexport * from \"./module\";\nexport * from \"./types\";\nexport * from \"./compat\";\n\nexport {\n KeyboardChatScrollView,\n KeyboardAvoidingView,\n KeyboardStickyView,\n KeyboardAwareScrollView,\n // keyboard toolbar\n KeyboardToolbar,\n DefaultKeyboardToolbarTheme,\n} from \"./components\";\nexport type {\n KeyboardChatScrollViewProps,\n KeyboardAvoidingViewProps,\n KeyboardStickyViewProps,\n KeyboardAwareScrollViewMode,\n KeyboardAwareScrollViewProps,\n KeyboardAwareScrollViewRef,\n KeyboardToolbarProps,\n} from \"./components\";\nexport { OverKeyboardView, KeyboardExtender } from \"./views\";\n"],"mappings":"AAAA,cAAc,YAAY;AAC1B,cAAc,YAAY;AAC1B,cAAc,WAAW;AACzB,cAAc,SAAS;AACvB,cAAc,aAAa;AAC3B,cAAc,UAAU;AACxB,cAAc,SAAS;AACvB,cAAc,UAAU;AAExB,SACEA,sBAAsB,EACtBC,oBAAoB,EACpBC,kBAAkB,EAClBC,uBAAuB;AACvB;AACAC,eAAe,EACfC,2BAA2B,QACtB,cAAc;AAUrB,SAASC,gBAAgB,EAAEC,gBAAgB,QAAQ,SAAS","ignoreList":[]}
@@ -1 +1 @@
1
- {"version":3,"names":["TurboModuleRegistry","get"],"sources":["NativeKeyboardController.ts"],"sourcesContent":["import { TurboModuleRegistry } from \"react-native\";\n\nimport type { TurboModule } from \"react-native\";\n\nexport interface Spec extends TurboModule {\n readonly getConstants: () => {};\n\n // methods\n setInputMode(mode: number): void;\n setDefaultMode(): void;\n preload(): void;\n dismiss(keepFocus: boolean, animated: boolean): void;\n setFocusTo(direction: string): void;\n viewPositionInWindow(viewTag: number): Promise<object>;\n\n // event emitter\n addListener: (eventName: string) => void;\n removeListeners: (count: number) => void;\n}\n\nexport default TurboModuleRegistry.get<Spec>(\"KeyboardController\");\n"],"mappings":"AAAA,SAASA,mBAAmB,QAAQ,cAAc;AAoBlD,eAAeA,mBAAmB,CAACC,GAAG,CAAO,oBAAoB,CAAC","ignoreList":[]}
1
+ {"version":3,"names":["TurboModuleRegistry","get"],"sources":["NativeKeyboardController.ts"],"sourcesContent":["import { TurboModuleRegistry } from \"react-native\";\n\nimport type { TurboModule } from \"react-native\";\n\nexport interface Spec extends TurboModule {\n readonly getConstants: () => {\n keyboardBorderRadius: number;\n };\n\n // methods\n setInputMode(mode: number): void;\n setDefaultMode(): void;\n preload(): void;\n dismiss(keepFocus: boolean, animated: boolean): void;\n setFocusTo(direction: string): void;\n viewPositionInWindow(viewTag: number): Promise<object>;\n\n // event emitter\n addListener: (eventName: string) => void;\n removeListeners: (count: number) => void;\n}\n\nexport default TurboModuleRegistry.get<Spec>(\"KeyboardController\");\n"],"mappings":"AAAA,SAASA,mBAAmB,QAAQ,cAAc;AAsBlD,eAAeA,mBAAmB,CAACC,GAAG,CAAO,oBAAoB,CAAC","ignoreList":[]}
@@ -1 +1 @@
1
- {"version":3,"names":[],"sources":["module.ts"],"sourcesContent":["import type { EmitterSubscription, TextInputProps } from \"react-native\";\n\n// Event module declarations\nexport type KeyboardControllerEvents =\n | \"keyboardWillShow\"\n | \"keyboardDidShow\"\n | \"keyboardWillHide\"\n | \"keyboardDidHide\";\nexport type KeyboardEventData = {\n /** Height of the keyboard. */\n height: number;\n /** Duration of the keyboard animation. */\n duration: number;\n /** Timestamp of the last keyboard event. */\n timestamp: number;\n /** Tag of the focused `TextInput`. */\n target: number;\n /** `keyboardType` property from focused `TextInput`. */\n type: NonNullable<TextInputProps[\"keyboardType\"]>;\n /** Keyboard appearance. Can be one of `dark` or `light`. */\n appearance: \"dark\" | \"light\";\n};\n/**\n * An object that represent current keyboard state.\n */\nexport type IKeyboardState = {\n /** Whether the keyboard is currently visible. */\n isVisible: boolean;\n} & KeyboardEventData;\nexport type KeyboardEventsModule = {\n /**\n * The `addListener` function connects a JavaScript function to an identified native\n * keyboard notification event.\n *\n * This function then returns the reference to the listener.\n *\n * `name` is the string that identifies the event you're listening for. This\n * can be any of the following:\n *\n * - `keyboardWillShow`;\n * - `keyboardDidShow`;\n * - `keyboardWillHide`;\n * - `keyboardDidHide`.\n */\n addListener: (\n name: KeyboardControllerEvents,\n cb: (e: KeyboardEventData) => void,\n ) => EmitterSubscription;\n};\nexport type Direction = \"next\" | \"prev\" | \"current\";\nexport type DismissOptions = {\n /**\n * A boolean property indicating whether focus should be kept on the input after dismissing the keyboard. Default is `false`.\n */\n keepFocus: boolean;\n /**\n * A boolean property controlling whether dismissal should be animated. Default is `true`.\n */\n animated: boolean;\n};\nexport type KeyboardControllerModule = {\n // android only\n /**\n * Sets default `windowSoftInputMode` (the one that declared in `AndroidManifest.xml`).\n *\n * @platform android\n * @see {@link https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/keyboard-controller#setdefaultmode-|docs} page for more details.\n */\n setDefaultMode: () => void;\n /**\n * Changes `windowSoftInputMode` on Android.\n *\n * @platform android\n * @see AndroidSoftInputModes for all possible values and {@link https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/keyboard-controller#setinputmode-|docs} page for more details.\n */\n setInputMode: (mode: number) => void;\n // ios only\n /**\n * A method that preloads the keyboard. It's useful when you want to avoid a delay when the user focuses the first input.\n *\n * @platform ios\n * @see {@link https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/keyboard-controller#preload-|docs} page for more details.\n */\n preload: () => void;\n // all platforms\n /**\n * Dismisses the active keyboard. Removes a focus by default, but allows to pass `{keepFocus: true}` to keep focus.\n * If you want to close keyboard immediately pass `{animated: false}`.\n *\n * @see {@link https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/keyboard-controller#dismiss|docs} page for more details.\n */\n dismiss: (options?: Partial<DismissOptions>) => Promise<void>;\n /**\n * Moves focus to the specified direction (`next`, `prev` or `current` to restore a focus).\n *\n * @see {@link https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/keyboard-controller#setfocusto|docs} page for more details.\n */\n setFocusTo: (direction: Direction) => void;\n /**\n * Whether the keyboard is fully visible.\n *\n * @see {@link https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/keyboard-controller#isvisible|docs} page for more details.\n */\n isVisible: () => boolean;\n /**\n * Method that returns current keyboard state.\n *\n * @see {@link https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/keyboard-controller#state|docs} page for more details.\n */\n state: () => KeyboardEventData;\n};\nexport type ViewPositionInWindowResult = {\n x: number;\n y: number;\n width: number;\n height: number;\n};\nexport type KeyboardControllerNativeModule = {\n // android only\n setDefaultMode: () => void;\n setInputMode: (mode: number) => void;\n // ios only\n preload: () => void;\n // all platforms\n dismiss: (keepFocus: boolean, animated: boolean) => void;\n setFocusTo: (direction: Direction) => void;\n viewPositionInWindow: (\n viewTag: number,\n ) => Promise<ViewPositionInWindowResult>;\n // native event module stuff\n addListener: (eventName: string) => void;\n removeListeners: (count: number) => void;\n};\n"],"mappings":"","ignoreList":[]}
1
+ {"version":3,"names":[],"sources":["module.ts"],"sourcesContent":["import type { EmitterSubscription, TextInputProps } from \"react-native\";\n\n// Event module declarations\nexport type KeyboardControllerEvents =\n | \"keyboardWillShow\"\n | \"keyboardDidShow\"\n | \"keyboardWillHide\"\n | \"keyboardDidHide\";\nexport type KeyboardEventData = {\n /** Height of the keyboard. */\n height: number;\n /** Duration of the keyboard animation. */\n duration: number;\n /** Timestamp of the last keyboard event. */\n timestamp: number;\n /** Tag of the focused `TextInput`. */\n target: number;\n /** `keyboardType` property from focused `TextInput`. */\n type: NonNullable<TextInputProps[\"keyboardType\"]>;\n /** Keyboard appearance. Can be one of `dark` or `light`. */\n appearance: \"dark\" | \"light\";\n};\n/**\n * An object that represent current keyboard state.\n */\nexport type IKeyboardState = {\n /** Whether the keyboard is currently visible. */\n isVisible: boolean;\n} & KeyboardEventData;\nexport type KeyboardEventsModule = {\n /**\n * The `addListener` function connects a JavaScript function to an identified native\n * keyboard notification event.\n *\n * This function then returns the reference to the listener.\n *\n * `name` is the string that identifies the event you're listening for. This\n * can be any of the following:\n *\n * - `keyboardWillShow`;\n * - `keyboardDidShow`;\n * - `keyboardWillHide`;\n * - `keyboardDidHide`.\n */\n addListener: (\n name: KeyboardControllerEvents,\n cb: (e: KeyboardEventData) => void,\n ) => EmitterSubscription;\n};\nexport type Direction = \"next\" | \"prev\" | \"current\";\nexport type DismissOptions = {\n /**\n * A boolean property indicating whether focus should be kept on the input after dismissing the keyboard. Default is `false`.\n */\n keepFocus: boolean;\n /**\n * A boolean property controlling whether dismissal should be animated. Default is `true`.\n */\n animated: boolean;\n};\nexport type KeyboardControllerModule = {\n // android only\n /**\n * Sets default `windowSoftInputMode` (the one that declared in `AndroidManifest.xml`).\n *\n * @platform android\n * @see {@link https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/keyboard-controller#setdefaultmode-|docs} page for more details.\n */\n setDefaultMode: () => void;\n /**\n * Changes `windowSoftInputMode` on Android.\n *\n * @platform android\n * @see AndroidSoftInputModes for all possible values and {@link https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/keyboard-controller#setinputmode-|docs} page for more details.\n */\n setInputMode: (mode: number) => void;\n // ios only\n /**\n * A method that preloads the keyboard. It's useful when you want to avoid a delay when the user focuses the first input.\n *\n * @platform ios\n * @see {@link https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/keyboard-controller#preload-|docs} page for more details.\n */\n preload: () => void;\n // all platforms\n /**\n * Dismisses the active keyboard. Removes a focus by default, but allows to pass `{keepFocus: true}` to keep focus.\n * If you want to close keyboard immediately pass `{animated: false}`.\n *\n * @see {@link https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/keyboard-controller#dismiss|docs} page for more details.\n */\n dismiss: (options?: Partial<DismissOptions>) => Promise<void>;\n /**\n * Moves focus to the specified direction (`next`, `prev` or `current` to restore a focus).\n *\n * @see {@link https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/keyboard-controller#setfocusto|docs} page for more details.\n */\n setFocusTo: (direction: Direction) => void;\n /**\n * Whether the keyboard is fully visible.\n *\n * @see {@link https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/keyboard-controller#isvisible|docs} page for more details.\n */\n isVisible: () => boolean;\n /**\n * Method that returns current keyboard state.\n *\n * @see {@link https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/keyboard-controller#state|docs} page for more details.\n */\n state: () => KeyboardEventData;\n};\nexport type ViewPositionInWindowResult = {\n x: number;\n y: number;\n width: number;\n height: number;\n};\nexport type KeyboardControllerNativeModule = {\n // android only\n setDefaultMode: () => void;\n setInputMode: (mode: number) => void;\n // ios only\n preload: () => void;\n // all platforms\n dismiss: (keepFocus: boolean, animated: boolean) => void;\n setFocusTo: (direction: Direction) => void;\n viewPositionInWindow: (\n viewTag: number,\n ) => Promise<ViewPositionInWindowResult>;\n // native event module stuff\n addListener: (eventName: string) => void;\n removeListeners: (count: number) => void;\n // constants\n getConstants: () => {\n keyboardBorderRadius: number;\n };\n};\n"],"mappings":"","ignoreList":[]}
@@ -1,21 +1,5 @@
1
1
  import React from "react";
2
- import type { AnimatedScrollViewComponent } from "../ScrollViewWithBottomPadding";
3
- import type { ScrollView, ScrollViewProps } from "react-native";
4
- export type KeyboardAwareScrollViewProps = {
5
- /** The distance between the keyboard and the caret inside a focused `TextInput` when the keyboard is shown. Default is `0`. */
6
- bottomOffset?: number;
7
- /** Prevents automatic scrolling of the `ScrollView` when the keyboard gets hidden, maintaining the current screen position. Default is `false`. */
8
- disableScrollOnKeyboardHide?: boolean;
9
- /** Controls whether this `KeyboardAwareScrollView` instance should take effect. Default is `true`. */
10
- enabled?: boolean;
11
- /** Adjusting the bottom spacing of KeyboardAwareScrollView. Default is `0`. */
12
- extraKeyboardSpace?: number;
13
- /** Custom component for `ScrollView`. Default is `ScrollView`. */
14
- ScrollViewComponent?: AnimatedScrollViewComponent;
15
- } & ScrollViewProps;
16
- export type KeyboardAwareScrollViewRef = {
17
- assureFocusedInputVisible: () => void;
18
- } & ScrollView;
2
+ import type { KeyboardAwareScrollViewRef } from "react-native-keyboard-controller";
19
3
  /**
20
4
  * A ScrollView component that automatically handles keyboard appearance and disappearance
21
5
  * by adjusting its content position to ensure the focused input remains visible.
@@ -34,17 +18,13 @@ export type KeyboardAwareScrollViewRef = {
34
18
  * ```
35
19
  */
36
20
  declare const KeyboardAwareScrollView: React.ForwardRefExoticComponent<{
37
- /** The distance between the keyboard and the caret inside a focused `TextInput` when the keyboard is shown. Default is `0`. */
38
21
  bottomOffset?: number;
39
- /** Prevents automatic scrolling of the `ScrollView` when the keyboard gets hidden, maintaining the current screen position. Default is `false`. */
40
22
  disableScrollOnKeyboardHide?: boolean;
41
- /** Controls whether this `KeyboardAwareScrollView` instance should take effect. Default is `true`. */
42
23
  enabled?: boolean;
43
- /** Adjusting the bottom spacing of KeyboardAwareScrollView. Default is `0`. */
44
24
  extraKeyboardSpace?: number;
45
- /** Custom component for `ScrollView`. Default is `ScrollView`. */
46
- ScrollViewComponent?: AnimatedScrollViewComponent;
47
- } & ScrollViewProps & {
25
+ mode?: import("react-native-keyboard-controller").KeyboardAwareScrollViewMode;
26
+ ScrollViewComponent?: import("../ScrollViewWithBottomPadding").AnimatedScrollViewComponent;
27
+ } & import("react-native").ScrollViewProps & {
48
28
  children?: React.ReactNode | undefined;
49
29
  } & React.RefAttributes<KeyboardAwareScrollViewRef>>;
50
30
  export default KeyboardAwareScrollView;
@@ -0,0 +1,27 @@
1
+ import type { AnimatedScrollViewComponent } from "../ScrollViewWithBottomPadding";
2
+ import type { ScrollView, ScrollViewProps } from "react-native";
3
+ export type KeyboardAwareScrollViewMode = "insets" | "layout";
4
+ export type KeyboardAwareScrollViewProps = {
5
+ /** The distance between the keyboard and the caret inside a focused `TextInput` when the keyboard is shown. Default is `0`. */
6
+ bottomOffset?: number;
7
+ /** Prevents automatic scrolling of the `ScrollView` when the keyboard gets hidden, maintaining the current screen position. Default is `false`. */
8
+ disableScrollOnKeyboardHide?: boolean;
9
+ /** Controls whether this `KeyboardAwareScrollView` instance should take effect. Default is `true`. */
10
+ enabled?: boolean;
11
+ /** Adjusting the bottom spacing of KeyboardAwareScrollView. Default is `0`. */
12
+ extraKeyboardSpace?: number;
13
+ /**
14
+ * Controls how keyboard space is created at the bottom of the `ScrollView`.
15
+ *
16
+ * - `"insets"` *(default)*: Extends the scrollable area via `contentInset` (iOS) and `ClippingScrollView` (Android). No layout reflow occurs — content positions remain stable during keyboard animation. Recommended for most use cases.
17
+ * - `"layout"`: Appends a spacer `View` as the last child of the `ScrollView`. The spacer participates in layout, so flex-based arrangements (e.g. `justifyContent: "space-between"`, `gap`) reflow naturally when the keyboard appears. Use this when you need content to physically rearrange around the keyboard space.
18
+ *
19
+ * Default is `"insets"`.
20
+ */
21
+ mode?: KeyboardAwareScrollViewMode;
22
+ /** Custom component for `ScrollView`. Default is `ScrollView`. */
23
+ ScrollViewComponent?: AnimatedScrollViewComponent;
24
+ } & ScrollViewProps;
25
+ export type KeyboardAwareScrollViewRef = {
26
+ assureFocusedInputVisible: () => void;
27
+ } & ScrollView;
@@ -5,6 +5,6 @@ export { default as KeyboardToolbar, DefaultKeyboardToolbarTheme, } from "./Keyb
5
5
  export { default as KeyboardChatScrollView } from "./KeyboardChatScrollView";
6
6
  export type { KeyboardAvoidingViewProps } from "./KeyboardAvoidingView";
7
7
  export type { KeyboardStickyViewProps } from "./KeyboardStickyView";
8
- export type { KeyboardAwareScrollViewProps, KeyboardAwareScrollViewRef, } from "./KeyboardAwareScrollView";
8
+ export type { KeyboardAwareScrollViewMode, KeyboardAwareScrollViewProps, KeyboardAwareScrollViewRef, } from "./KeyboardAwareScrollView/types";
9
9
  export type { KeyboardToolbarProps } from "./KeyboardToolbar";
10
10
  export type { KeyboardChatScrollViewProps } from "./KeyboardChatScrollView/types";
@@ -14,3 +14,4 @@ export declare enum AndroidSoftInputModes {
14
14
  SOFT_INPUT_STATE_UNSPECIFIED = 0,
15
15
  SOFT_INPUT_STATE_VISIBLE = 4
16
16
  }
17
+ export declare const KEYBOARD_BORDER_RADIUS: number;
@@ -7,5 +7,5 @@ export * from "./module";
7
7
  export * from "./types";
8
8
  export * from "./compat";
9
9
  export { KeyboardChatScrollView, KeyboardAvoidingView, KeyboardStickyView, KeyboardAwareScrollView, KeyboardToolbar, DefaultKeyboardToolbarTheme, } from "./components";
10
- export type { KeyboardChatScrollViewProps, KeyboardAvoidingViewProps, KeyboardStickyViewProps, KeyboardAwareScrollViewProps, KeyboardAwareScrollViewRef, KeyboardToolbarProps, } from "./components";
10
+ export type { KeyboardChatScrollViewProps, KeyboardAvoidingViewProps, KeyboardStickyViewProps, KeyboardAwareScrollViewMode, KeyboardAwareScrollViewProps, KeyboardAwareScrollViewRef, KeyboardToolbarProps, } from "./components";
11
11
  export { OverKeyboardView, KeyboardExtender } from "./views";
@@ -1,6 +1,8 @@
1
1
  import type { TurboModule } from "react-native";
2
2
  export interface Spec extends TurboModule {
3
- readonly getConstants: () => {};
3
+ readonly getConstants: () => {
4
+ keyboardBorderRadius: number;
5
+ };
4
6
  setInputMode(mode: number): void;
5
7
  setDefaultMode(): void;
6
8
  preload(): void;
@@ -112,4 +112,7 @@ export type KeyboardControllerNativeModule = {
112
112
  viewPositionInWindow: (viewTag: number) => Promise<ViewPositionInWindowResult>;
113
113
  addListener: (eventName: string) => void;
114
114
  removeListeners: (count: number) => void;
115
+ getConstants: () => {
116
+ keyboardBorderRadius: number;
117
+ };
115
118
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-keyboard-controller",
3
- "version": "1.21.2",
3
+ "version": "1.21.4",
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",
package/src/bindings.ts CHANGED
@@ -27,6 +27,9 @@ export const KeyboardControllerNative: KeyboardControllerNativeModule = {
27
27
  Promise.resolve({ x: 0, y: 0, width: 0, height: 0 }),
28
28
  addListener: NOOP,
29
29
  removeListeners: NOOP,
30
+ getConstants: () => ({
31
+ keyboardBorderRadius: 0,
32
+ }),
30
33
  };
31
34
  /**
32
35
  * An event emitter that provides a way to subscribe to next keyboard events:
@@ -12,6 +12,7 @@ import Reanimated, {
12
12
  scrollTo,
13
13
  useAnimatedReaction,
14
14
  useAnimatedRef,
15
+ useAnimatedStyle,
15
16
  useDerivedValue,
16
17
  useSharedValue,
17
18
  } from "react-native-reanimated";
@@ -30,34 +31,15 @@ import ScrollViewWithBottomPadding from "../ScrollViewWithBottomPadding";
30
31
  import { useSmoothKeyboardHandler } from "./useSmoothKeyboardHandler";
31
32
  import { debounce, scrollDistanceWithRespectToSnapPoints } from "./utils";
32
33
 
33
- import type { AnimatedScrollViewComponent } from "../ScrollViewWithBottomPadding";
34
- import type {
35
- LayoutChangeEvent,
36
- ScrollView,
37
- ScrollViewProps,
38
- } from "react-native";
34
+ import type { LayoutChangeEvent, ScrollView } from "react-native";
39
35
  import type {
40
36
  FocusedInputLayoutChangedEvent,
41
37
  FocusedInputSelectionChangedEvent,
38
+ KeyboardAwareScrollViewProps,
39
+ KeyboardAwareScrollViewRef,
42
40
  NativeEvent,
43
41
  } from "react-native-keyboard-controller";
44
42
 
45
- export type KeyboardAwareScrollViewProps = {
46
- /** The distance between the keyboard and the caret inside a focused `TextInput` when the keyboard is shown. Default is `0`. */
47
- bottomOffset?: number;
48
- /** Prevents automatic scrolling of the `ScrollView` when the keyboard gets hidden, maintaining the current screen position. Default is `false`. */
49
- disableScrollOnKeyboardHide?: boolean;
50
- /** Controls whether this `KeyboardAwareScrollView` instance should take effect. Default is `true`. */
51
- enabled?: boolean;
52
- /** Adjusting the bottom spacing of KeyboardAwareScrollView. Default is `0`. */
53
- extraKeyboardSpace?: number;
54
- /** Custom component for `ScrollView`. Default is `ScrollView`. */
55
- ScrollViewComponent?: AnimatedScrollViewComponent;
56
- } & ScrollViewProps;
57
- export type KeyboardAwareScrollViewRef = {
58
- assureFocusedInputVisible: () => void;
59
- } & ScrollView;
60
-
61
43
  // Everything begins from `onStart` handler. This handler is called every time,
62
44
  // when keyboard changes its size or when focused `TextInput` was changed. In
63
45
  // this handler we are calculating/memoizing values which later will be used
@@ -123,6 +105,7 @@ const KeyboardAwareScrollView = forwardRef<
123
105
  disableScrollOnKeyboardHide = false,
124
106
  enabled = true,
125
107
  extraKeyboardSpace = 0,
108
+ mode = "insets",
126
109
  ScrollViewComponent = Reanimated.ScrollView,
127
110
  snapToOffsets,
128
111
  ...rest
@@ -151,6 +134,7 @@ const KeyboardAwareScrollView = forwardRef<
151
134
  useSharedValue<FocusedInputSelectionChangedEvent | null>(null);
152
135
  const ghostViewSpace = useSharedValue(-1);
153
136
  const pendingSelectionForFocus = useSharedValue(false);
137
+ const selectionUpdatedSinceHide = useSharedValue(false);
154
138
  const scrollViewPageY = useSharedValue(0);
155
139
 
156
140
  const { height } = useWindowDimensions();
@@ -237,30 +221,39 @@ const KeyboardAwareScrollView = forwardRef<
237
221
  },
238
222
  [bottomOffset, enabled, height, snapToOffsets],
239
223
  );
240
- const removeGhostPadding = useCallback((e: number) => {
241
- "worklet";
224
+ const removeGhostPadding = useCallback(
225
+ (e: number) => {
226
+ "worklet";
242
227
 
243
- // new `ScrollViewWithBottomPadding` behavior: if we hide keyboard and we are in the end of `ScrollView`
244
- // then we always need to scroll back, because we apply a padding that doesn't change layout, so we will
245
- // not have auto scroll back in this case
246
- if (!keyboardWillAppear.value && ghostViewSpace.value > 0) {
247
- scrollTo(
248
- scrollViewAnimatedRef,
249
- 0,
250
- scrollPosition.value -
251
- interpolate(
252
- e,
253
- [initialKeyboardSize.value, keyboardHeight.value],
254
- [ghostViewSpace.value, 0],
255
- ),
256
- false,
257
- );
228
+ // layout mode: the spacer view participates in layout, so the ScrollView
229
+ // reflows naturally when it shrinks no manual scroll correction needed.
230
+ if (mode === "layout") {
231
+ return false;
232
+ }
258
233
 
259
- return true;
260
- }
234
+ // insets mode: `ScrollViewWithBottomPadding` extends scrollable area without
235
+ // changing layout, so when the keyboard hides and we're at the end of the
236
+ // ScrollView we must manually scroll back.
237
+ if (!keyboardWillAppear.value && ghostViewSpace.value > 0) {
238
+ scrollTo(
239
+ scrollViewAnimatedRef,
240
+ 0,
241
+ scrollPosition.value -
242
+ interpolate(
243
+ e,
244
+ [initialKeyboardSize.value, keyboardHeight.value],
245
+ [ghostViewSpace.value, 0],
246
+ ),
247
+ false,
248
+ );
249
+
250
+ return true;
251
+ }
261
252
 
262
- return false;
263
- }, []);
253
+ return false;
254
+ },
255
+ [mode],
256
+ );
264
257
  const performScrollWithPositionRestoration = useCallback(
265
258
  (newPosition: number) => {
266
259
  "worklet";
@@ -339,8 +332,9 @@ const KeyboardAwareScrollView = forwardRef<
339
332
  const latestSelection = lastSelection.value?.selection;
340
333
 
341
334
  lastSelection.value = e;
335
+ selectionUpdatedSinceHide.value = true;
342
336
 
343
- if (e.target !== lastTarget) {
337
+ if (e.target !== lastTarget || pendingSelectionForFocus.value) {
344
338
  if (pendingSelectionForFocus.value) {
345
339
  // selection arrived after onStart - complete the deferred setup
346
340
  pendingSelectionForFocus.value = false;
@@ -420,22 +414,36 @@ const KeyboardAwareScrollView = forwardRef<
420
414
  scrollPosition.value = position.value;
421
415
  // just persist height - later will be used in interpolation
422
416
  keyboardHeight.value = e.height;
423
- // and update keyboard spacer size
424
- syncKeyboardFrame(e);
417
+
418
+ // insets mode: set the full contentInset upfront so that maybeScroll
419
+ // calculations are correct from the very first onMove frame.
420
+ // layout mode: do NOT set it here — the spacer must grow frame-by-frame
421
+ // in onMove to avoid a premature full-height jump before the keyboard moves.
422
+ if (mode === "insets") {
423
+ syncKeyboardFrame(e);
424
+ }
425
425
  }
426
426
 
427
427
  // focus was changed
428
428
  if (focusWasChanged) {
429
429
  tag.value = e.target;
430
430
 
431
- if (lastSelection.value?.target === e.target) {
432
- // selection arrived before onStart - use it to update layout
431
+ if (
432
+ lastSelection.value?.target === e.target &&
433
+ selectionUpdatedSinceHide.value
434
+ ) {
435
+ // fresh selection arrived before onStart - use it to update layout
433
436
  updateLayoutFromSelection();
434
437
  pendingSelectionForFocus.value = false;
435
438
  } else {
436
- // selection hasn't arrived yet for the new target.
437
- // use input layout as-is; will be refined when selection arrives.
438
- if (input.value) {
439
+ // selection hasn't arrived yet for the new target (iOS 15),
440
+ // or it's stale from previous session (Android refocus same input).
441
+ // Use stale selection as best-effort fallback if available for same target,
442
+ // otherwise fall back to full input layout.
443
+ // Will be corrected if a fresh onSelectionChange arrives.
444
+ if (lastSelection.value?.target === e.target) {
445
+ updateLayoutFromSelection();
446
+ } else if (input.value) {
439
447
  layout.value = input.value;
440
448
  }
441
449
  pendingSelectionForFocus.value = true;
@@ -454,13 +462,15 @@ const KeyboardAwareScrollView = forwardRef<
454
462
  }
455
463
  }
456
464
 
457
- ghostViewSpace.value =
458
- position.value +
459
- scrollViewLayout.value.height -
460
- scrollViewContentSize.value.height;
465
+ if (mode === "insets") {
466
+ ghostViewSpace.value =
467
+ position.value +
468
+ scrollViewLayout.value.height -
469
+ scrollViewContentSize.value.height;
461
470
 
462
- if (ghostViewSpace.value > 0) {
463
- scrollPosition.value = position.value;
471
+ if (ghostViewSpace.value > 0) {
472
+ scrollPosition.value = position.value;
473
+ }
464
474
  }
465
475
  },
466
476
  onMove: (e) => {
@@ -470,6 +480,11 @@ const KeyboardAwareScrollView = forwardRef<
470
480
  return;
471
481
  }
472
482
 
483
+ // layout mode: drive the spacer view animation frame-by-frame
484
+ if (mode === "layout") {
485
+ syncKeyboardFrame(e);
486
+ }
487
+
473
488
  // if the user has set disableScrollOnKeyboardHide, only auto-scroll when the keyboard opens
474
489
  if (!disableScrollOnKeyboardHide || keyboardWillAppear.value) {
475
490
  maybeScroll(e.height);
@@ -484,13 +499,21 @@ const KeyboardAwareScrollView = forwardRef<
484
499
  scrollPosition.value = position.value;
485
500
 
486
501
  if (e.height === 0) {
487
- lastSelection.value = null;
502
+ selectionUpdatedSinceHide.value = false;
503
+ } else if (keyboardWillAppear.value) {
504
+ // keyboard fully shown after appearing from hidden state — clear
505
+ // pending flag to prevent leaking into next focus-change session.
506
+ // Only when the keyboard was actually appearing (not a focus switch
507
+ // with same keyboard height), otherwise we'd clear the flag before
508
+ // onSelectionChange has a chance to process it.
509
+ pendingSelectionForFocus.value = false;
488
510
  }
489
511
 
490
512
  syncKeyboardFrame(e);
491
513
  },
492
514
  },
493
515
  [
516
+ mode,
494
517
  maybeScroll,
495
518
  removeGhostPadding,
496
519
  disableScrollOnKeyboardHide,
@@ -557,6 +580,31 @@ const KeyboardAwareScrollView = forwardRef<
557
580
  () => (enabled ? currentKeyboardFrameHeight.value : 0),
558
581
  [enabled],
559
582
  );
583
+ // layout mode only: a spacer view whose paddingBottom grows with the keyboard.
584
+ // The `+ 1` ensures the scroll view never reaches its absolute end during animation,
585
+ // avoiding the layout recalculation that triggers on every frame at the boundary.
586
+ // see: https://github.com/kirillzyusko/react-native-keyboard-controller/pull/342
587
+ const layoutSpacerStyle = useAnimatedStyle(
588
+ () =>
589
+ enabled && mode === "layout"
590
+ ? { paddingBottom: currentKeyboardFrameHeight.value + 1 }
591
+ : {},
592
+ [enabled, mode],
593
+ );
594
+
595
+ if (mode === "layout") {
596
+ return (
597
+ <ScrollViewComponent
598
+ ref={onRef}
599
+ {...rest}
600
+ scrollEventThrottle={16}
601
+ onLayout={onScrollViewLayout}
602
+ >
603
+ {children}
604
+ {enabled && <Reanimated.View style={layoutSpacerStyle} />}
605
+ </ScrollViewComponent>
606
+ );
607
+ }
560
608
 
561
609
  return (
562
610
  <ScrollViewWithBottomPadding
@@ -0,0 +1,29 @@
1
+ import type { AnimatedScrollViewComponent } from "../ScrollViewWithBottomPadding";
2
+ import type { ScrollView, ScrollViewProps } from "react-native";
3
+
4
+ export type KeyboardAwareScrollViewMode = "insets" | "layout";
5
+
6
+ export type KeyboardAwareScrollViewProps = {
7
+ /** The distance between the keyboard and the caret inside a focused `TextInput` when the keyboard is shown. Default is `0`. */
8
+ bottomOffset?: number;
9
+ /** Prevents automatic scrolling of the `ScrollView` when the keyboard gets hidden, maintaining the current screen position. Default is `false`. */
10
+ disableScrollOnKeyboardHide?: boolean;
11
+ /** Controls whether this `KeyboardAwareScrollView` instance should take effect. Default is `true`. */
12
+ enabled?: boolean;
13
+ /** Adjusting the bottom spacing of KeyboardAwareScrollView. Default is `0`. */
14
+ extraKeyboardSpace?: number;
15
+ /**
16
+ * Controls how keyboard space is created at the bottom of the `ScrollView`.
17
+ *
18
+ * - `"insets"` *(default)*: Extends the scrollable area via `contentInset` (iOS) and `ClippingScrollView` (Android). No layout reflow occurs — content positions remain stable during keyboard animation. Recommended for most use cases.
19
+ * - `"layout"`: Appends a spacer `View` as the last child of the `ScrollView`. The spacer participates in layout, so flex-based arrangements (e.g. `justifyContent: "space-between"`, `gap`) reflow naturally when the keyboard appears. Use this when you need content to physically rearrange around the keyboard space.
20
+ *
21
+ * Default is `"insets"`.
22
+ */
23
+ mode?: KeyboardAwareScrollViewMode;
24
+ /** Custom component for `ScrollView`. Default is `ScrollView`. */
25
+ ScrollViewComponent?: AnimatedScrollViewComponent;
26
+ } & ScrollViewProps;
27
+ export type KeyboardAwareScrollViewRef = {
28
+ assureFocusedInputVisible: () => void;
29
+ } & ScrollView;
@@ -1,4 +1,4 @@
1
- import { Platform } from "react-native";
1
+ import { KEYBOARD_BORDER_RADIUS } from "../../constants";
2
2
 
3
3
  import type { HEX } from "./types";
4
4
 
@@ -10,6 +10,5 @@ export const TEST_ID_KEYBOARD_TOOLBAR_DONE = `${TEST_ID_KEYBOARD_TOOLBAR}.done`;
10
10
 
11
11
  export const KEYBOARD_TOOLBAR_HEIGHT = 42;
12
12
  export const DEFAULT_OPACITY: HEX = "FF";
13
- export const KEYBOARD_HAS_ROUNDED_CORNERS =
14
- Platform.OS === "ios" && parseInt(Platform.Version, 10) >= 26;
13
+ export const KEYBOARD_HAS_ROUNDED_CORNERS = KEYBOARD_BORDER_RADIUS > 0;
15
14
  export const OPENED_OFFSET = KEYBOARD_HAS_ROUNDED_CORNERS ? -11 : 0;
@@ -9,8 +9,9 @@ export { default as KeyboardChatScrollView } from "./KeyboardChatScrollView";
9
9
  export type { KeyboardAvoidingViewProps } from "./KeyboardAvoidingView";
10
10
  export type { KeyboardStickyViewProps } from "./KeyboardStickyView";
11
11
  export type {
12
+ KeyboardAwareScrollViewMode,
12
13
  KeyboardAwareScrollViewProps,
13
14
  KeyboardAwareScrollViewRef,
14
- } from "./KeyboardAwareScrollView";
15
+ } from "./KeyboardAwareScrollView/types";
15
16
  export type { KeyboardToolbarProps } from "./KeyboardToolbar";
16
17
  export type { KeyboardChatScrollViewProps } from "./KeyboardChatScrollView/types";
package/src/constants.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { KeyboardControllerNative } from "./bindings";
2
+
1
3
  // copied from `android.view.WindowManager.LayoutParams`
2
4
  export enum AndroidSoftInputModes {
3
5
  SOFT_INPUT_ADJUST_NOTHING = 48,
@@ -17,3 +19,5 @@ export enum AndroidSoftInputModes {
17
19
  SOFT_INPUT_STATE_UNSPECIFIED = 0,
18
20
  SOFT_INPUT_STATE_VISIBLE = 4,
19
21
  }
22
+ export const KEYBOARD_BORDER_RADIUS =
23
+ KeyboardControllerNative.getConstants().keyboardBorderRadius;
package/src/index.ts CHANGED
@@ -20,6 +20,7 @@ export type {
20
20
  KeyboardChatScrollViewProps,
21
21
  KeyboardAvoidingViewProps,
22
22
  KeyboardStickyViewProps,
23
+ KeyboardAwareScrollViewMode,
23
24
  KeyboardAwareScrollViewProps,
24
25
  KeyboardAwareScrollViewRef,
25
26
  KeyboardToolbarProps,
@@ -3,7 +3,9 @@ import { TurboModuleRegistry } from "react-native";
3
3
  import type { TurboModule } from "react-native";
4
4
 
5
5
  export interface Spec extends TurboModule {
6
- readonly getConstants: () => {};
6
+ readonly getConstants: () => {
7
+ keyboardBorderRadius: number;
8
+ };
7
9
 
8
10
  // methods
9
11
  setInputMode(mode: number): void;
@@ -130,4 +130,8 @@ export type KeyboardControllerNativeModule = {
130
130
  // native event module stuff
131
131
  addListener: (eventName: string) => void;
132
132
  removeListeners: (count: number) => void;
133
+ // constants
134
+ getConstants: () => {
135
+ keyboardBorderRadius: number;
136
+ };
133
137
  };