react-native-keyboard-controller 1.7.0 → 1.9.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 (133) hide show
  1. package/README.md +2 -0
  2. package/android/gradle.properties +1 -1
  3. package/android/src/fabric/java/com/reactnativekeyboardcontroller/KeyboardControllerViewManager.kt +5 -0
  4. package/android/src/main/java/com/reactnativekeyboardcontroller/events/FocusedInputLayoutChangedEvent.kt +41 -0
  5. package/android/src/main/java/com/reactnativekeyboardcontroller/extensions/ThemedReactContext.kt +9 -0
  6. package/android/src/main/java/com/reactnativekeyboardcontroller/extensions/View.kt +7 -0
  7. package/android/src/main/java/com/reactnativekeyboardcontroller/listeners/FocusedInputLayoutObserver.kt +93 -0
  8. package/android/src/main/java/com/reactnativekeyboardcontroller/{KeyboardAnimationCallback.kt → listeners/KeyboardAnimationCallback.kt} +86 -55
  9. package/android/src/main/java/com/reactnativekeyboardcontroller/managers/KeyboardControllerViewManagerImpl.kt +6 -0
  10. package/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt +120 -50
  11. package/android/src/paper/java/com/reactnativekeyboardcontroller/KeyboardControllerViewManager.kt +5 -0
  12. package/ios/Extensions.swift +7 -0
  13. package/ios/KeyboardController.xcodeproj/project.pbxproj +41 -7
  14. package/ios/events/FocusedInputLayoutChangedEvent.h +16 -0
  15. package/ios/events/FocusedInputLayoutChangedEvent.m +75 -0
  16. package/ios/observers/FocusedInputLayoutObserver.swift +136 -0
  17. package/ios/{KeyboardControllerView.mm → views/KeyboardControllerView.mm} +54 -3
  18. package/ios/{KeyboardControllerViewManager.mm → views/KeyboardControllerViewManager.mm} +7 -0
  19. package/ios/{KeyboardControllerViewManager.swift → views/KeyboardControllerViewManager.swift} +25 -1
  20. package/jest/index.js +16 -0
  21. package/lib/commonjs/animated.js +34 -6
  22. package/lib/commonjs/animated.js.map +1 -1
  23. package/lib/commonjs/bindings.js.map +1 -1
  24. package/lib/commonjs/bindings.native.js.map +1 -1
  25. package/lib/commonjs/components/KeyboardAvoidingView/hooks.js +9 -1
  26. package/lib/commonjs/components/KeyboardAvoidingView/hooks.js.map +1 -1
  27. package/lib/commonjs/components/KeyboardAvoidingView/index.js +7 -4
  28. package/lib/commonjs/components/KeyboardAvoidingView/index.js.map +1 -1
  29. package/lib/commonjs/components/KeyboardStickyView/index.js +44 -0
  30. package/lib/commonjs/components/KeyboardStickyView/index.js.map +1 -0
  31. package/lib/commonjs/components/index.js +7 -0
  32. package/lib/commonjs/components/index.js.map +1 -1
  33. package/lib/commonjs/constants.js.map +1 -1
  34. package/lib/commonjs/context.js +9 -4
  35. package/lib/commonjs/context.js.map +1 -1
  36. package/lib/commonjs/hooks.js +16 -1
  37. package/lib/commonjs/hooks.js.map +1 -1
  38. package/lib/commonjs/index.js +8 -2
  39. package/lib/commonjs/index.js.map +1 -1
  40. package/lib/commonjs/internal.js.map +1 -1
  41. package/lib/commonjs/monkey-patch.android.js +32 -20
  42. package/lib/commonjs/monkey-patch.android.js.map +1 -1
  43. package/lib/commonjs/monkey-patch.js +11 -1
  44. package/lib/commonjs/monkey-patch.js.map +1 -1
  45. package/lib/commonjs/reanimated.js +5 -2
  46. package/lib/commonjs/reanimated.js.map +1 -1
  47. package/lib/commonjs/reanimated.native.js +18 -1
  48. package/lib/commonjs/reanimated.native.js.map +1 -1
  49. package/lib/commonjs/replicas.js +1 -1
  50. package/lib/commonjs/replicas.js.map +1 -1
  51. package/lib/commonjs/specs/KeyboardControllerViewNativeComponent.js.map +1 -1
  52. package/lib/commonjs/specs/KeyboardGestureAreaNativeComponent.js.map +1 -1
  53. package/lib/commonjs/specs/NativeKeyboardController.js.map +1 -1
  54. package/lib/commonjs/specs/NativeStatusBarManagerCompat.js.map +1 -1
  55. package/lib/commonjs/types.js.map +1 -1
  56. package/lib/module/animated.js +37 -9
  57. package/lib/module/animated.js.map +1 -1
  58. package/lib/module/bindings.js.map +1 -1
  59. package/lib/module/bindings.native.js +1 -1
  60. package/lib/module/bindings.native.js.map +1 -1
  61. package/lib/module/components/KeyboardAvoidingView/hooks.js +9 -1
  62. package/lib/module/components/KeyboardAvoidingView/hooks.js.map +1 -1
  63. package/lib/module/components/KeyboardAvoidingView/index.js +9 -6
  64. package/lib/module/components/KeyboardAvoidingView/index.js.map +1 -1
  65. package/lib/module/components/KeyboardStickyView/index.js +35 -0
  66. package/lib/module/components/KeyboardStickyView/index.js.map +1 -0
  67. package/lib/module/components/index.js +1 -0
  68. package/lib/module/components/index.js.map +1 -1
  69. package/lib/module/constants.js.map +1 -1
  70. package/lib/module/context.js +9 -4
  71. package/lib/module/context.js.map +1 -1
  72. package/lib/module/hooks.js +14 -1
  73. package/lib/module/hooks.js.map +1 -1
  74. package/lib/module/index.js +1 -2
  75. package/lib/module/index.js.map +1 -1
  76. package/lib/module/internal.js.map +1 -1
  77. package/lib/module/monkey-patch.android.js +26 -20
  78. package/lib/module/monkey-patch.android.js.map +1 -1
  79. package/lib/module/monkey-patch.js +3 -0
  80. package/lib/module/monkey-patch.js.map +1 -1
  81. package/lib/module/reanimated.js +3 -1
  82. package/lib/module/reanimated.js.map +1 -1
  83. package/lib/module/reanimated.native.js +16 -0
  84. package/lib/module/reanimated.native.js.map +1 -1
  85. package/lib/module/replicas.js +2 -2
  86. package/lib/module/replicas.js.map +1 -1
  87. package/lib/module/specs/KeyboardControllerViewNativeComponent.js.map +1 -1
  88. package/lib/module/specs/KeyboardGestureAreaNativeComponent.js.map +1 -1
  89. package/lib/module/specs/NativeKeyboardController.js.map +1 -1
  90. package/lib/module/specs/NativeStatusBarManagerCompat.js.map +1 -1
  91. package/lib/module/types.js.map +1 -1
  92. package/lib/typescript/animated.d.ts +8 -1
  93. package/lib/typescript/components/KeyboardAvoidingView/hooks.d.ts +1 -0
  94. package/lib/typescript/components/KeyboardAvoidingView/index.d.ts +2 -1
  95. package/lib/typescript/components/KeyboardStickyView/index.d.ts +20 -0
  96. package/lib/typescript/components/index.d.ts +1 -0
  97. package/lib/typescript/context.d.ts +6 -3
  98. package/lib/typescript/hooks.d.ts +9 -2
  99. package/lib/typescript/index.d.ts +1 -2
  100. package/lib/typescript/internal.d.ts +1 -1
  101. package/lib/typescript/monkey-patch.android.d.ts +2 -1
  102. package/lib/typescript/monkey-patch.d.ts +2 -0
  103. package/lib/typescript/reanimated.d.ts +2 -1
  104. package/lib/typescript/reanimated.native.d.ts +2 -1
  105. package/lib/typescript/specs/KeyboardControllerViewNativeComponent.d.ts +13 -0
  106. package/lib/typescript/types.d.ts +17 -0
  107. package/package.json +9 -28
  108. package/src/animated.tsx +53 -9
  109. package/src/bindings.native.ts +1 -1
  110. package/src/bindings.ts +3 -1
  111. package/src/components/KeyboardAvoidingView/hooks.ts +9 -1
  112. package/src/components/KeyboardAvoidingView/index.tsx +15 -13
  113. package/src/components/KeyboardStickyView/index.tsx +63 -0
  114. package/src/components/index.ts +1 -0
  115. package/src/constants.ts +2 -0
  116. package/src/context.ts +16 -5
  117. package/src/hooks.ts +16 -6
  118. package/src/index.ts +1 -3
  119. package/src/internal.ts +3 -1
  120. package/src/monkey-patch.android.ts +27 -21
  121. package/src/monkey-patch.ts +3 -0
  122. package/src/reanimated.native.ts +30 -1
  123. package/src/reanimated.ts +13 -2
  124. package/src/replicas.ts +2 -2
  125. package/src/specs/KeyboardControllerViewNativeComponent.ts +16 -1
  126. package/src/specs/KeyboardGestureAreaNativeComponent.ts +2 -1
  127. package/src/specs/NativeKeyboardController.ts +2 -1
  128. package/src/specs/NativeStatusBarManagerCompat.ts +2 -1
  129. package/src/types.ts +28 -1
  130. /package/ios/{KeyboardMoveEvent.h → events/KeyboardMoveEvent.h} +0 -0
  131. /package/ios/{KeyboardMoveEvent.m → events/KeyboardMoveEvent.m} +0 -0
  132. /package/ios/{KeyboardMovementObserver.swift → observers/KeyboardMovementObserver.swift} +0 -0
  133. /package/ios/{KeyboardControllerView.h → views/KeyboardControllerView.h} +0 -0
package/src/animated.tsx CHANGED
@@ -1,17 +1,24 @@
1
- import React, { useMemo } from 'react';
2
- import { Animated, Platform, StyleSheet, ViewStyle } from 'react-native';
1
+ import React, { useEffect, useMemo, useState } from 'react';
2
+ import { Animated, Platform, StyleSheet } from 'react-native';
3
3
  import Reanimated, { useSharedValue } from 'react-native-reanimated';
4
4
 
5
- import { KeyboardContext } from './context';
6
- import { useSharedHandlers, useAnimatedValue } from './internal';
7
5
  import { KeyboardControllerView } from './bindings';
8
- import { useAnimatedKeyboardHandler } from './reanimated';
6
+ import { KeyboardContext } from './context';
7
+ import { useAnimatedValue, useSharedHandlers } from './internal';
8
+ import { applyMonkeyPatch, revertMonkeyPatch } from './monkey-patch';
9
+ import {
10
+ useAnimatedKeyboardHandler,
11
+ useFocusedInputHandler,
12
+ } from './reanimated';
9
13
 
14
+ import type { KeyboardAnimationContext } from './context';
10
15
  import type {
16
+ FocusedInputLayoutChangedEvent,
11
17
  KeyboardControllerProps,
12
18
  KeyboardHandler,
13
19
  NativeEvent,
14
20
  } from './types';
21
+ import type { ViewStyle } from 'react-native';
15
22
 
16
23
  const KeyboardControllerViewAnimated = Reanimated.createAnimatedComponent(
17
24
  Animated.createAnimatedComponent(
@@ -54,28 +61,41 @@ type KeyboardProviderProps = {
54
61
  * @platform android
55
62
  */
56
63
  navigationBarTranslucent?: boolean;
64
+ /**
65
+ * A boolean prop indicating whether the module is enabled. It indicate only initial state,
66
+ * i. e. if you try to change this prop after component mount it will not have any effect.
67
+ * To change the property in runtime use `useKeyboardController` hook and `setEnabled` method.
68
+ * Defaults to `true`.
69
+ */
70
+ enabled?: boolean;
57
71
  };
58
72
 
59
73
  export const KeyboardProvider = ({
60
74
  children,
61
75
  statusBarTranslucent,
62
76
  navigationBarTranslucent,
77
+ enabled: initiallyEnabled = true,
63
78
  }: KeyboardProviderProps) => {
79
+ const [enabled, setEnabled] = useState(initiallyEnabled);
64
80
  // animated values
65
81
  const progress = useAnimatedValue(0);
66
82
  const height = useAnimatedValue(0);
67
83
  // shared values
68
84
  const progressSV = useSharedValue(0);
69
85
  const heightSV = useSharedValue(0);
86
+ const layout = useSharedValue<FocusedInputLayoutChangedEvent | null>(null);
70
87
  const { setHandlers, broadcast } = useSharedHandlers<KeyboardHandler>();
71
88
  // memo
72
- const context = useMemo(
89
+ const context = useMemo<KeyboardAnimationContext>(
73
90
  () => ({
91
+ enabled,
74
92
  animated: { progress: progress, height: Animated.multiply(height, -1) },
75
93
  reanimated: { progress: progressSV, height: heightSV },
94
+ layout,
76
95
  setHandlers,
96
+ setEnabled,
77
97
  }),
78
- []
98
+ [enabled]
79
99
  );
80
100
  const style = useMemo(
81
101
  () => [
@@ -108,7 +128,7 @@ export const KeyboardProvider = ({
108
128
  heightSV.value = -event.height;
109
129
  }
110
130
  };
111
- const handler = useAnimatedKeyboardHandler(
131
+ const keyboardHandler = useAnimatedKeyboardHandler(
112
132
  {
113
133
  onKeyboardMoveStart: (event: NativeEvent) => {
114
134
  'worklet';
@@ -136,14 +156,38 @@ export const KeyboardProvider = ({
136
156
  },
137
157
  []
138
158
  );
159
+ const inputHandler = useFocusedInputHandler(
160
+ {
161
+ onFocusedInputLayoutChanged: (e) => {
162
+ 'worklet';
163
+
164
+ if (e.target !== -1) {
165
+ layout.value = e;
166
+ } else {
167
+ layout.value = null;
168
+ }
169
+ },
170
+ },
171
+ []
172
+ );
173
+ // effects
174
+ useEffect(() => {
175
+ if (enabled) {
176
+ applyMonkeyPatch();
177
+ } else {
178
+ revertMonkeyPatch();
179
+ }
180
+ }, [enabled]);
139
181
 
140
182
  return (
141
183
  <KeyboardContext.Provider value={context}>
142
184
  <KeyboardControllerViewAnimated
143
- onKeyboardMoveReanimated={handler}
185
+ enabled={enabled}
186
+ onKeyboardMoveReanimated={keyboardHandler}
144
187
  onKeyboardMoveStart={Platform.OS === 'ios' ? onKeyboardMove : undefined}
145
188
  onKeyboardMove={Platform.OS === 'android' ? onKeyboardMove : undefined}
146
189
  onKeyboardMoveInteractive={onKeyboardMove}
190
+ onFocusedInputLayoutChangedReanimated={inputHandler}
147
191
  navigationBarTranslucent={navigationBarTranslucent}
148
192
  statusBarTranslucent={statusBarTranslucent}
149
193
  // @ts-expect-error https://github.com/software-mansion/react-native-reanimated/pull/4923
@@ -1,4 +1,4 @@
1
- import { Platform, NativeEventEmitter } from 'react-native';
1
+ import { NativeEventEmitter, Platform } from 'react-native';
2
2
 
3
3
  import type {
4
4
  KeyboardControllerModule,
package/src/bindings.ts CHANGED
@@ -1,10 +1,12 @@
1
- import { EmitterSubscription, View } from 'react-native';
1
+ import { View } from 'react-native';
2
+
2
3
  import type {
3
4
  KeyboardControllerModule,
4
5
  KeyboardControllerProps,
5
6
  KeyboardEventsModule,
6
7
  KeyboardGestureAreaProps,
7
8
  } from './types';
9
+ import type { EmitterSubscription } from 'react-native';
8
10
 
9
11
  const NOOP = () => {};
10
12
  export const KeyboardController: KeyboardControllerModule = {
@@ -1,10 +1,12 @@
1
1
  import { useSharedValue } from 'react-native-reanimated';
2
+
2
3
  import { useKeyboardHandler } from '../../hooks';
3
4
 
4
5
  export const useKeyboardAnimation = () => {
5
6
  const heightWhenOpened = useSharedValue(0);
6
7
  const height = useSharedValue(0);
7
8
  const progress = useSharedValue(0);
9
+ const isClosed = useSharedValue(true);
8
10
 
9
11
  useKeyboardHandler(
10
12
  {
@@ -12,6 +14,7 @@ export const useKeyboardAnimation = () => {
12
14
  'worklet';
13
15
 
14
16
  if (e.height > 0) {
17
+ isClosed.value = false;
15
18
  heightWhenOpened.value = e.height;
16
19
  }
17
20
  },
@@ -21,9 +24,14 @@ export const useKeyboardAnimation = () => {
21
24
  progress.value = e.progress;
22
25
  height.value = e.height;
23
26
  },
27
+ onEnd: (e) => {
28
+ 'worklet';
29
+
30
+ isClosed.value = e.height === 0;
31
+ },
24
32
  },
25
33
  []
26
34
  );
27
35
 
28
- return { height, progress, heightWhenOpened };
36
+ return { height, progress, heightWhenOpened, isClosed };
29
37
  };
@@ -1,19 +1,18 @@
1
1
  import React, { forwardRef, useCallback, useMemo } from 'react';
2
- import {
3
- LayoutRectangle,
4
- useWindowDimensions,
5
- View,
6
- ViewProps,
7
- } from 'react-native';
2
+ import { View, useWindowDimensions } from 'react-native';
8
3
  import Reanimated, {
4
+ interpolate,
5
+ runOnUI,
9
6
  useAnimatedStyle,
10
- useWorkletCallback,
11
- useSharedValue,
12
7
  useDerivedValue,
13
- interpolate,
8
+ useSharedValue,
9
+ useWorkletCallback,
14
10
  } from 'react-native-reanimated';
11
+
15
12
  import { useKeyboardAnimation } from './hooks';
16
13
 
14
+ import type { LayoutRectangle, ViewProps } from 'react-native';
15
+
17
16
  type Props = {
18
17
  /**
19
18
  * Specify how to react to the presence of the keyboard.
@@ -76,11 +75,14 @@ const KeyboardAvoidingView = forwardRef<View, React.PropsWithChildren<Props>>(
76
75
  return Math.max(frame.value.y + frame.value.height - keyboardY, 0);
77
76
  }, [screenHeight, keyboardVerticalOffset]);
78
77
 
78
+ const onLayoutWorklet = useWorkletCallback((layout: LayoutRectangle) => {
79
+ if (keyboard.isClosed.value) {
80
+ initialFrame.value = layout;
81
+ }
82
+ });
79
83
  const onLayout = useCallback<NonNullable<ViewProps['onLayout']>>(
80
84
  (e) => {
81
- if (initialFrame.value === null) {
82
- initialFrame.value = e.nativeEvent.layout;
83
- }
85
+ runOnUI(onLayoutWorklet)(e.nativeEvent.layout);
84
86
  onLayoutProps?.(e);
85
87
  },
86
88
  [onLayoutProps]
@@ -96,7 +98,7 @@ const KeyboardAvoidingView = forwardRef<View, React.PropsWithChildren<Props>>(
96
98
 
97
99
  switch (behavior) {
98
100
  case 'height':
99
- if (bottomHeight > 0) {
101
+ if (!keyboard.isClosed.value) {
100
102
  return {
101
103
  height: frame.value.height - bottomHeight,
102
104
  flex: 0,
@@ -0,0 +1,63 @@
1
+ import React, { forwardRef, useMemo } from 'react';
2
+ import Reanimated, {
3
+ interpolate,
4
+ useAnimatedStyle,
5
+ } from 'react-native-reanimated';
6
+
7
+ import { useReanimatedKeyboardAnimation } from '../../hooks';
8
+
9
+ import type { View, ViewProps } from 'react-native';
10
+
11
+ type KeyboardStickyViewProps = {
12
+ /**
13
+ * Specify additional offset to the view for given keyboard state.
14
+ */
15
+ offset?: {
16
+ /**
17
+ * Adds additional `translateY` when keyboard is close. By default `0`.
18
+ */
19
+ closed?: number;
20
+ /**
21
+ * Adds additional `translateY` when keyboard is open. By default `0`.
22
+ */
23
+ opened?: number;
24
+ };
25
+ } & ViewProps;
26
+
27
+ const KeyboardStickyView = forwardRef<
28
+ View,
29
+ React.PropsWithChildren<KeyboardStickyViewProps>
30
+ >(
31
+ (
32
+ { children, offset: { closed = 0, opened = 0 } = {}, style, ...props },
33
+ ref
34
+ ) => {
35
+ const { height, progress } = useReanimatedKeyboardAnimation();
36
+
37
+ const stickyViewStyle = useAnimatedStyle(() => {
38
+ const offset = interpolate(progress.value, [0, 1], [closed, opened]);
39
+
40
+ return {
41
+ transform: [{ translateY: height.value + offset }],
42
+ };
43
+ }, [closed, opened]);
44
+
45
+ const styles = useMemo(
46
+ () => [style, stickyViewStyle],
47
+ [style, stickyViewStyle]
48
+ );
49
+
50
+ return (
51
+ <Reanimated.View
52
+ // @ts-expect-error because `ref` from reanimated is not compatible with react-native
53
+ ref={ref}
54
+ style={styles}
55
+ {...props}
56
+ >
57
+ {children}
58
+ </Reanimated.View>
59
+ );
60
+ }
61
+ );
62
+
63
+ export default KeyboardStickyView;
@@ -1 +1,2 @@
1
1
  export { default as KeyboardAvoidingView } from './KeyboardAvoidingView';
2
+ export { default as KeyboardStickyView } from './KeyboardStickyView';
package/src/constants.ts CHANGED
@@ -12,6 +12,8 @@ export enum AndroidSoftInputModes {
12
12
  SOFT_INPUT_STATE_ALWAYS_VISIBLE = 5,
13
13
  SOFT_INPUT_STATE_HIDDEN = 2,
14
14
  SOFT_INPUT_STATE_UNCHANGED = 1,
15
+ // temporarily disable this rule to avoid breaking changes.
16
+ // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values
15
17
  SOFT_INPUT_STATE_UNSPECIFIED = 0,
16
18
  SOFT_INPUT_STATE_VISIBLE = 4,
17
19
  }
package/src/context.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  import { createContext, useContext } from 'react';
2
2
  import { Animated } from 'react-native';
3
3
 
4
+ import type { FocusedInputLayoutChangedEvent, KeyboardHandlers } from './types';
5
+ import type React from 'react';
4
6
  import type { SharedValue } from 'react-native-reanimated';
5
- import type { KeyboardHandlers } from './types';
6
7
 
7
8
  export type AnimatedContext = {
8
9
  progress: Animated.Value;
@@ -13,18 +14,26 @@ export type ReanimatedContext = {
13
14
  height: SharedValue<number>;
14
15
  };
15
16
  export type KeyboardAnimationContext = {
17
+ enabled: boolean;
16
18
  animated: AnimatedContext;
17
19
  reanimated: ReanimatedContext;
20
+ layout: SharedValue<FocusedInputLayoutChangedEvent | null>;
18
21
  setHandlers: (handlers: KeyboardHandlers) => void;
22
+ setEnabled: React.Dispatch<React.SetStateAction<boolean>>;
19
23
  };
20
24
  const NOOP = () => {};
21
- const DEFAULT_SHARED_VALUE: SharedValue<number> = {
22
- value: 0,
25
+ const withSharedValue = <T>(value: T): SharedValue<T> => ({
26
+ value,
23
27
  addListener: NOOP,
24
28
  removeListener: NOOP,
25
29
  modify: NOOP,
26
- };
30
+ });
31
+ const DEFAULT_SHARED_VALUE = withSharedValue(0);
32
+ const DEFAULT_LAYOUT = withSharedValue<FocusedInputLayoutChangedEvent | null>(
33
+ null
34
+ );
27
35
  const defaultContext: KeyboardAnimationContext = {
36
+ enabled: true,
28
37
  animated: {
29
38
  progress: new Animated.Value(0),
30
39
  height: new Animated.Value(0),
@@ -33,7 +42,9 @@ const defaultContext: KeyboardAnimationContext = {
33
42
  progress: DEFAULT_SHARED_VALUE,
34
43
  height: DEFAULT_SHARED_VALUE,
35
44
  },
36
- setHandlers: () => {},
45
+ layout: DEFAULT_LAYOUT,
46
+ setHandlers: NOOP,
47
+ setEnabled: NOOP,
37
48
  };
38
49
  export const KeyboardContext = createContext(defaultContext);
39
50
  export const useKeyboardContext = () => {
package/src/hooks.ts CHANGED
@@ -1,15 +1,13 @@
1
- import { DependencyList, useEffect } from 'react';
1
+ import { useEffect } from 'react';
2
2
 
3
- import {
4
- AnimatedContext,
5
- ReanimatedContext,
6
- useKeyboardContext,
7
- } from './context';
8
3
  import { KeyboardController } from './bindings';
9
4
  import { AndroidSoftInputModes } from './constants';
5
+ import { useKeyboardContext } from './context';
10
6
  import { uuid } from './utils';
11
7
 
8
+ import type { AnimatedContext, ReanimatedContext } from './context';
12
9
  import type { KeyboardHandler } from './types';
10
+ import type { DependencyList } from 'react';
13
11
 
14
12
  export const useResizeMode = () => {
15
13
  useEffect(() => {
@@ -59,3 +57,15 @@ export function useKeyboardHandler(
59
57
  useResizeMode();
60
58
  useGenericKeyboardHandler(handler, deps);
61
59
  }
60
+
61
+ export function useKeyboardController() {
62
+ const context = useKeyboardContext();
63
+
64
+ return { setEnabled: context.setEnabled, enabled: context.enabled };
65
+ }
66
+
67
+ export function useReanimatedFocusedInput() {
68
+ const context = useKeyboardContext();
69
+
70
+ return { input: context.layout };
71
+ }
package/src/index.ts CHANGED
@@ -1,5 +1,3 @@
1
- import './monkey-patch';
2
-
3
1
  export * from './bindings';
4
2
  export * from './animated';
5
3
  export * from './replicas';
@@ -8,4 +6,4 @@ export * from './hooks';
8
6
  export * from './constants';
9
7
  export * from './types';
10
8
 
11
- export { KeyboardAvoidingView } from './components';
9
+ export { KeyboardAvoidingView, KeyboardStickyView } from './components';
package/src/internal.ts CHANGED
@@ -15,7 +15,9 @@ import type { Handlers, NativeEvent } from './types';
15
15
  * onEvent2: () => {},
16
16
  * }
17
17
  */
18
- export function useSharedHandlers<T extends Record<string, Function>>() {
18
+ export function useSharedHandlers<
19
+ T extends Record<string, (event: NativeEvent) => void>
20
+ >() {
19
21
  const handlers = useSharedValue<Handlers<T>>({});
20
22
  const jsHandlers = useRef<Handlers<T>>({});
21
23
 
@@ -1,33 +1,39 @@
1
1
  // @ts-expect-error because there is no corresponding type definition
2
2
  import * as NativeAndroidManager from 'react-native/Libraries/Components/StatusBar/NativeStatusBarManagerAndroid';
3
3
 
4
+ const DefaultNativeAndroidManager = NativeAndroidManager.default;
4
5
  const getConstants = NativeAndroidManager.default.getConstants;
6
+ const RCTStatusBarManagerCompat =
7
+ require('./specs/NativeStatusBarManagerCompat').default;
5
8
 
6
9
  // On Android < 11 RN uses legacy API which breaks EdgeToEdge mode in RN, so
7
10
  // in order to use library on all available platforms we have to monkey patch
8
11
  // default RN implementation and use modern `WindowInsetsControllerCompat`.
9
- const RCTStatusBarManagerCompat =
10
- require('./specs/NativeStatusBarManagerCompat').default;
11
- NativeAndroidManager.default = {
12
- getConstants,
13
- setColor(color: number, animated: boolean): void {
14
- RCTStatusBarManagerCompat.setColor(color, animated);
15
- },
12
+ export const applyMonkeyPatch = () => {
13
+ NativeAndroidManager.default = {
14
+ getConstants,
15
+ setColor(color: number, animated: boolean): void {
16
+ RCTStatusBarManagerCompat.setColor(color, animated);
17
+ },
16
18
 
17
- setTranslucent(translucent: boolean): void {
18
- RCTStatusBarManagerCompat.setTranslucent(translucent);
19
- },
19
+ setTranslucent(translucent: boolean): void {
20
+ RCTStatusBarManagerCompat.setTranslucent(translucent);
21
+ },
20
22
 
21
- /**
22
- * - statusBarStyles can be:
23
- * - 'default'
24
- * - 'dark-content'
25
- */
26
- setStyle(statusBarStyle?: string): void {
27
- RCTStatusBarManagerCompat.setStyle(statusBarStyle);
28
- },
23
+ /**
24
+ * - statusBarStyles can be:
25
+ * - 'default'
26
+ * - 'dark-content'
27
+ */
28
+ setStyle(statusBarStyle?: string): void {
29
+ RCTStatusBarManagerCompat.setStyle(statusBarStyle);
30
+ },
29
31
 
30
- setHidden(hidden: boolean): void {
31
- RCTStatusBarManagerCompat.setHidden(hidden);
32
- },
32
+ setHidden(hidden: boolean): void {
33
+ RCTStatusBarManagerCompat.setHidden(hidden);
34
+ },
35
+ };
36
+ };
37
+ export const revertMonkeyPatch = () => {
38
+ NativeAndroidManager.default = DefaultNativeAndroidManager;
33
39
  };
@@ -1 +1,4 @@
1
1
  // stub for all platforms
2
+ const NOOP = () => {};
3
+ export const applyMonkeyPatch = NOOP;
4
+ export const revertMonkeyPatch = NOOP;
@@ -1,6 +1,12 @@
1
1
  import { useEvent, useHandler } from 'react-native-reanimated';
2
2
 
3
- import type { EventWithName, KeyboardHandlerHook, NativeEvent } from './types';
3
+ import type {
4
+ EventWithName,
5
+ FocusedInputHandlerHook,
6
+ FocusedInputLayoutChangedEvent,
7
+ KeyboardHandlerHook,
8
+ NativeEvent,
9
+ } from './types';
4
10
 
5
11
  export const useAnimatedKeyboardHandler: KeyboardHandlerHook<
6
12
  Record<string, unknown>,
@@ -49,3 +55,26 @@ export const useAnimatedKeyboardHandler: KeyboardHandlerHook<
49
55
  doDependenciesDiffer
50
56
  );
51
57
  };
58
+
59
+ export const useFocusedInputHandler: FocusedInputHandlerHook<
60
+ Record<string, unknown>,
61
+ EventWithName<FocusedInputLayoutChangedEvent>
62
+ > = (handlers, dependencies) => {
63
+ const { context, doDependenciesDiffer } = useHandler(handlers, dependencies);
64
+
65
+ return useEvent(
66
+ (event) => {
67
+ 'worklet';
68
+ const { onFocusedInputLayoutChanged } = handlers;
69
+
70
+ if (
71
+ onFocusedInputLayoutChanged &&
72
+ event.eventName.endsWith('onFocusedInputLayoutChanged')
73
+ ) {
74
+ onFocusedInputLayoutChanged(event, context);
75
+ }
76
+ },
77
+ ['onFocusedInputLayoutChanged'],
78
+ doDependenciesDiffer
79
+ );
80
+ };
package/src/reanimated.ts CHANGED
@@ -1,6 +1,17 @@
1
- import type { EventWithName, KeyboardHandlerHook, NativeEvent } from './types';
1
+ import type {
2
+ EventWithName,
3
+ FocusedInputHandlerHook,
4
+ FocusedInputLayoutChangedEvent,
5
+ KeyboardHandlerHook,
6
+ NativeEvent,
7
+ } from './types';
2
8
 
9
+ const NOOP = () => () => {};
3
10
  export const useAnimatedKeyboardHandler: KeyboardHandlerHook<
4
11
  Record<string, unknown>,
5
12
  EventWithName<NativeEvent>
6
- > = () => () => {};
13
+ > = NOOP;
14
+ export const useFocusedInputHandler: FocusedInputHandlerHook<
15
+ Record<string, unknown>,
16
+ EventWithName<FocusedInputLayoutChangedEvent>
17
+ > = NOOP;
package/src/replicas.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { useRef, useEffect, useMemo } from 'react';
1
+ import { useEffect, useMemo, useRef } from 'react';
2
2
  import { Animated, Easing, Keyboard, Platform } from 'react-native';
3
3
  import {
4
4
  runOnUI,
@@ -8,10 +8,10 @@ import {
8
8
  useWorkletCallback,
9
9
  withSpring,
10
10
  } from 'react-native-reanimated';
11
- import { useReanimatedKeyboardAnimation } from './hooks';
12
11
 
13
12
  import { KeyboardController } from './bindings';
14
13
  import { AndroidSoftInputModes } from './constants';
14
+ import { useReanimatedKeyboardAnimation } from './hooks';
15
15
 
16
16
  const availableOSEventType = Platform.OS === 'ios' ? 'Will' : 'Did';
17
17
 
@@ -1,3 +1,5 @@
1
+ import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
2
+
1
3
  import type { HostComponent } from 'react-native';
2
4
  import type { ViewProps } from 'react-native/Libraries/Components/View/ViewPropTypes';
3
5
  import type {
@@ -5,7 +7,6 @@ import type {
5
7
  Double,
6
8
  Int32,
7
9
  } from 'react-native/Libraries/Types/CodegenTypes';
8
- import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
9
10
 
10
11
  type KeyboardMoveEvent = Readonly<{
11
12
  height: Double;
@@ -14,8 +15,21 @@ type KeyboardMoveEvent = Readonly<{
14
15
  target: Int32;
15
16
  }>;
16
17
 
18
+ type FocusedInputLayoutChangedEvent = Readonly<{
19
+ target: Int32;
20
+ layout: {
21
+ x: Double;
22
+ y: Double;
23
+ width: Double;
24
+ height: Double;
25
+ absoluteX: Double;
26
+ absoluteY: Double;
27
+ };
28
+ }>;
29
+
17
30
  export interface NativeProps extends ViewProps {
18
31
  // props
32
+ enabled?: boolean;
19
33
  statusBarTranslucent?: boolean;
20
34
  navigationBarTranslucent?: boolean;
21
35
  // callbacks
@@ -23,6 +37,7 @@ export interface NativeProps extends ViewProps {
23
37
  onKeyboardMove?: DirectEventHandler<KeyboardMoveEvent>;
24
38
  onKeyboardMoveEnd?: DirectEventHandler<KeyboardMoveEvent>;
25
39
  onKeyboardMoveInteractive?: DirectEventHandler<KeyboardMoveEvent>;
40
+ onFocusedInputLayoutChanged?: DirectEventHandler<FocusedInputLayoutChangedEvent>;
26
41
  }
27
42
 
28
43
  export default codegenNativeComponent<NativeProps>(
@@ -1,7 +1,8 @@
1
+ import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
2
+
1
3
  import type { HostComponent } from 'react-native';
2
4
  import type { ViewProps } from 'react-native/Libraries/Components/View/ViewPropTypes';
3
5
  import type { WithDefault } from 'react-native/Libraries/Types/CodegenTypes';
4
- import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
5
6
 
6
7
  export interface NativeProps extends ViewProps {
7
8
  interpolator?: WithDefault<'linear' | 'ios', 'linear'>;
@@ -1,6 +1,7 @@
1
- import type { TurboModule } from 'react-native';
2
1
  import { TurboModuleRegistry } from 'react-native';
3
2
 
3
+ import type { TurboModule } from 'react-native';
4
+
4
5
  export interface Spec extends TurboModule {
5
6
  readonly getConstants: () => {};
6
7
 
@@ -1,6 +1,7 @@
1
- import type { TurboModule } from 'react-native';
2
1
  import { TurboModuleRegistry } from 'react-native';
3
2
 
3
+ import type { TurboModule } from 'react-native';
4
+
4
5
  export interface Spec extends TurboModule {
5
6
  readonly getConstants: () => {};
6
7