react-native-keyboard-controller 1.21.1 → 1.21.3

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 (42) hide show
  1. package/android/src/main/java/com/reactnativekeyboardcontroller/views/ClippingScrollViewDecoratorView.kt +18 -1
  2. package/ios/views/KeyboardExtenderContainerView.swift +4 -1
  3. package/lib/commonjs/architecture.js +8 -0
  4. package/lib/commonjs/architecture.js.map +1 -0
  5. package/lib/commonjs/components/KeyboardAwareScrollView/index.js +25 -16
  6. package/lib/commonjs/components/KeyboardAwareScrollView/index.js.map +1 -1
  7. package/lib/commonjs/components/KeyboardAwareScrollView/types.js +6 -0
  8. package/lib/commonjs/components/KeyboardAwareScrollView/types.js.map +1 -0
  9. package/lib/commonjs/components/KeyboardChatScrollView/index.js +1 -0
  10. package/lib/commonjs/components/KeyboardChatScrollView/index.js.map +1 -1
  11. package/lib/commonjs/components/KeyboardChatScrollView/useExtraContentPadding/index.js +22 -2
  12. package/lib/commonjs/components/KeyboardChatScrollView/useExtraContentPadding/index.js.map +1 -1
  13. package/lib/commonjs/components/ScrollViewWithBottomPadding/styles.js +2 -1
  14. package/lib/commonjs/components/ScrollViewWithBottomPadding/styles.js.map +1 -1
  15. package/lib/commonjs/components/index.js.map +1 -1
  16. package/lib/module/architecture.js +2 -0
  17. package/lib/module/architecture.js.map +1 -0
  18. package/lib/module/components/KeyboardAwareScrollView/index.js +25 -16
  19. package/lib/module/components/KeyboardAwareScrollView/index.js.map +1 -1
  20. package/lib/module/components/KeyboardAwareScrollView/types.js +2 -0
  21. package/lib/module/components/KeyboardAwareScrollView/types.js.map +1 -0
  22. package/lib/module/components/KeyboardChatScrollView/index.js +1 -0
  23. package/lib/module/components/KeyboardChatScrollView/index.js.map +1 -1
  24. package/lib/module/components/KeyboardChatScrollView/useExtraContentPadding/index.js +22 -2
  25. package/lib/module/components/KeyboardChatScrollView/useExtraContentPadding/index.js.map +1 -1
  26. package/lib/module/components/ScrollViewWithBottomPadding/styles.js +2 -1
  27. package/lib/module/components/ScrollViewWithBottomPadding/styles.js.map +1 -1
  28. package/lib/module/components/index.js.map +1 -1
  29. package/lib/typescript/architecture.d.ts +1 -0
  30. package/lib/typescript/components/KeyboardAwareScrollView/index.d.ts +3 -24
  31. package/lib/typescript/components/KeyboardAwareScrollView/types.d.ts +17 -0
  32. package/lib/typescript/components/KeyboardChatScrollView/useExtraContentPadding/index.d.ts +2 -0
  33. package/lib/typescript/components/ScrollViewWithBottomPadding/styles.d.ts +2 -1
  34. package/lib/typescript/components/index.d.ts +1 -1
  35. package/package.json +1 -1
  36. package/src/architecture.ts +1 -0
  37. package/src/components/KeyboardAwareScrollView/index.tsx +30 -37
  38. package/src/components/KeyboardAwareScrollView/types.ts +18 -0
  39. package/src/components/KeyboardChatScrollView/index.tsx +1 -0
  40. package/src/components/KeyboardChatScrollView/useExtraContentPadding/index.ts +28 -2
  41. package/src/components/ScrollViewWithBottomPadding/styles.ts +2 -1
  42. package/src/components/index.ts +1 -1
@@ -30,34 +30,15 @@ import ScrollViewWithBottomPadding from "../ScrollViewWithBottomPadding";
30
30
  import { useSmoothKeyboardHandler } from "./useSmoothKeyboardHandler";
31
31
  import { debounce, scrollDistanceWithRespectToSnapPoints } from "./utils";
32
32
 
33
- import type { AnimatedScrollViewComponent } from "../ScrollViewWithBottomPadding";
34
- import type {
35
- LayoutChangeEvent,
36
- ScrollView,
37
- ScrollViewProps,
38
- } from "react-native";
33
+ import type { LayoutChangeEvent, ScrollView } from "react-native";
39
34
  import type {
40
35
  FocusedInputLayoutChangedEvent,
41
36
  FocusedInputSelectionChangedEvent,
37
+ KeyboardAwareScrollViewProps,
38
+ KeyboardAwareScrollViewRef,
42
39
  NativeEvent,
43
40
  } from "react-native-keyboard-controller";
44
41
 
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
42
  // Everything begins from `onStart` handler. This handler is called every time,
62
43
  // when keyboard changes its size or when focused `TextInput` was changed. In
63
44
  // this handler we are calculating/memoizing values which later will be used
@@ -151,6 +132,7 @@ const KeyboardAwareScrollView = forwardRef<
151
132
  useSharedValue<FocusedInputSelectionChangedEvent | null>(null);
152
133
  const ghostViewSpace = useSharedValue(-1);
153
134
  const pendingSelectionForFocus = useSharedValue(false);
135
+ const selectionUpdatedSinceHide = useSharedValue(false);
154
136
  const scrollViewPageY = useSharedValue(0);
155
137
 
156
138
  const { height } = useWindowDimensions();
@@ -339,8 +321,9 @@ const KeyboardAwareScrollView = forwardRef<
339
321
  const latestSelection = lastSelection.value?.selection;
340
322
 
341
323
  lastSelection.value = e;
324
+ selectionUpdatedSinceHide.value = true;
342
325
 
343
- if (e.target !== lastTarget) {
326
+ if (e.target !== lastTarget || pendingSelectionForFocus.value) {
344
327
  if (pendingSelectionForFocus.value) {
345
328
  // selection arrived after onStart - complete the deferred setup
346
329
  pendingSelectionForFocus.value = false;
@@ -416,25 +399,34 @@ const KeyboardAwareScrollView = forwardRef<
416
399
  keyboardWillChangeSize ||
417
400
  focusWasChanged
418
401
  ) {
419
- syncKeyboardFrame(e);
420
402
  // persist scroll value
421
403
  scrollPosition.value = position.value;
422
404
  // just persist height - later will be used in interpolation
423
405
  keyboardHeight.value = e.height;
406
+ // and update keyboard spacer size
407
+ syncKeyboardFrame(e);
424
408
  }
425
409
 
426
410
  // focus was changed
427
411
  if (focusWasChanged) {
428
412
  tag.value = e.target;
429
413
 
430
- if (lastSelection.value?.target === e.target) {
431
- // selection arrived before onStart - use it to update layout
414
+ if (
415
+ lastSelection.value?.target === e.target &&
416
+ selectionUpdatedSinceHide.value
417
+ ) {
418
+ // fresh selection arrived before onStart - use it to update layout
432
419
  updateLayoutFromSelection();
433
420
  pendingSelectionForFocus.value = false;
434
421
  } else {
435
- // selection hasn't arrived yet for the new target.
436
- // use input layout as-is; will be refined when selection arrives.
437
- if (input.value) {
422
+ // selection hasn't arrived yet for the new target (iOS 15),
423
+ // or it's stale from previous session (Android refocus same input).
424
+ // Use stale selection as best-effort fallback if available for same target,
425
+ // otherwise fall back to full input layout.
426
+ // Will be corrected if a fresh onSelectionChange arrives.
427
+ if (lastSelection.value?.target === e.target) {
428
+ updateLayoutFromSelection();
429
+ } else if (input.value) {
438
430
  layout.value = input.value;
439
431
  }
440
432
  pendingSelectionForFocus.value = true;
@@ -483,7 +475,14 @@ const KeyboardAwareScrollView = forwardRef<
483
475
  scrollPosition.value = position.value;
484
476
 
485
477
  if (e.height === 0) {
486
- lastSelection.value = null;
478
+ selectionUpdatedSinceHide.value = false;
479
+ } else if (keyboardWillAppear.value) {
480
+ // keyboard fully shown after appearing from hidden state — clear
481
+ // pending flag to prevent leaking into next focus-change session.
482
+ // Only when the keyboard was actually appearing (not a focus switch
483
+ // with same keyboard height), otherwise we'd clear the flag before
484
+ // onSelectionChange has a chance to process it.
485
+ pendingSelectionForFocus.value = false;
487
486
  }
488
487
 
489
488
  syncKeyboardFrame(e);
@@ -552,14 +551,8 @@ const KeyboardAwareScrollView = forwardRef<
552
551
  [],
553
552
  );
554
553
 
555
- // animations become choppy when scrolling to the end of the `ScrollView` (when the last input is focused)
556
- // this happens because the layout recalculates on every frame. To avoid this we slightly increase padding
557
- // by `+1`. In this way we assure, that `scrollTo` will never scroll to the end, because it uses interpolation
558
- // from 0 to `keyboardHeight`, and here our padding is `keyboardHeight + 1`. It allows us not to re-run layout
559
- // re-calculation on every animation frame and it helps to achieve smooth animation.
560
- // see: https://github.com/kirillzyusko/react-native-keyboard-controller/pull/342
561
554
  const padding = useDerivedValue(
562
- () => (enabled ? currentKeyboardFrameHeight.value + 1 : 0),
555
+ () => (enabled ? currentKeyboardFrameHeight.value : 0),
563
556
  [enabled],
564
557
  );
565
558
 
@@ -0,0 +1,18 @@
1
+ import type { AnimatedScrollViewComponent } from "../ScrollViewWithBottomPadding";
2
+ import type { ScrollView, ScrollViewProps } from "react-native";
3
+
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;
@@ -69,6 +69,7 @@ const KeyboardChatScrollView = forwardRef<
69
69
  scroll,
70
70
  layout,
71
71
  size,
72
+ contentOffsetY,
72
73
  inverted,
73
74
  keyboardLiftBehavior,
74
75
  freeze,
@@ -1,5 +1,8 @@
1
+ import { useCallback } from "react";
2
+ import { Platform } from "react-native";
1
3
  import { scrollTo, useAnimatedReaction } from "react-native-reanimated";
2
4
 
5
+ import { IS_FABRIC } from "../../../architecture";
3
6
  import { isScrollAtEnd, shouldShiftContent } from "../useChatKeyboard/helpers";
4
7
 
5
8
  import type { KeyboardLiftBehavior } from "../useChatKeyboard/types";
@@ -19,6 +22,8 @@ type UseExtraContentPaddingOptions = {
19
22
  layout: SharedValue<{ width: number; height: number }>;
20
23
  /** Total content dimensions. */
21
24
  size: SharedValue<{ width: number; height: number }>;
25
+ /** IOS only — when provided, sets contentOffset atomically with contentInset. */
26
+ contentOffsetY?: SharedValue<number>;
22
27
  inverted: boolean;
23
28
  keyboardLiftBehavior: KeyboardLiftBehavior;
24
29
  freeze: boolean;
@@ -47,11 +52,32 @@ function useExtraContentPadding(options: UseExtraContentPaddingOptions): void {
47
52
  scroll,
48
53
  layout,
49
54
  size,
55
+ contentOffsetY,
50
56
  inverted,
51
57
  keyboardLiftBehavior,
52
58
  freeze,
53
59
  } = options;
54
60
 
61
+ const scrollToTarget = useCallback(
62
+ (target: number) => {
63
+ "worklet";
64
+
65
+ if (contentOffsetY && IS_FABRIC) {
66
+ // eslint-disable-next-line react-compiler/react-compiler
67
+ contentOffsetY.value = target;
68
+ } else if (Platform.OS === "android") {
69
+ // Defer scrollTo so the animatedProps inset commit lands first;
70
+ // otherwise the native ScrollView clamps to the old range.
71
+ requestAnimationFrame(() => {
72
+ scrollTo(scrollViewRef, 0, target, false);
73
+ });
74
+ } else {
75
+ scrollTo(scrollViewRef, 0, target, false);
76
+ }
77
+ },
78
+ [scrollViewRef, contentOffsetY],
79
+ );
80
+
55
81
  useAnimatedReaction(
56
82
  () => extraContentPadding.value,
57
83
  (current, previous) => {
@@ -104,7 +130,7 @@ function useExtraContentPadding(options: UseExtraContentPaddingOptions): void {
104
130
  if (inverted) {
105
131
  const target = Math.max(scroll.value - effectiveDelta, -currentTotal);
106
132
 
107
- scrollTo(scrollViewRef, 0, target, false);
133
+ scrollToTarget(target);
108
134
  } else {
109
135
  const maxScroll = Math.max(
110
136
  size.value.height - layout.value.height + currentTotal,
@@ -112,7 +138,7 @@ function useExtraContentPadding(options: UseExtraContentPaddingOptions): void {
112
138
  );
113
139
  const target = Math.min(scroll.value + effectiveDelta, maxScroll);
114
140
 
115
- scrollTo(scrollViewRef, 0, target, false);
141
+ scrollToTarget(target);
116
142
  }
117
143
  },
118
144
  [inverted, keyboardLiftBehavior, freeze],
@@ -2,7 +2,8 @@ import { StyleSheet } from "react-native";
2
2
 
3
3
  const styles = StyleSheet.create({
4
4
  container: {
5
- flex: 1,
5
+ flexGrow: 1,
6
+ flexShrink: 1,
6
7
  },
7
8
  });
8
9
 
@@ -11,6 +11,6 @@ export type { KeyboardStickyViewProps } from "./KeyboardStickyView";
11
11
  export type {
12
12
  KeyboardAwareScrollViewProps,
13
13
  KeyboardAwareScrollViewRef,
14
- } from "./KeyboardAwareScrollView";
14
+ } from "./KeyboardAwareScrollView/types";
15
15
  export type { KeyboardToolbarProps } from "./KeyboardToolbar";
16
16
  export type { KeyboardChatScrollViewProps } from "./KeyboardChatScrollView/types";