react-native-universal-keyboard-aware-scrollview 1.0.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 (84) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +387 -0
  3. package/android/app/build.gradle +182 -0
  4. package/android/app/debug.keystore +0 -0
  5. package/android/app/proguard-rules.pro +14 -0
  6. package/android/app/src/debug/AndroidManifest.xml +7 -0
  7. package/android/app/src/debugOptimized/AndroidManifest.xml +7 -0
  8. package/android/app/src/main/AndroidManifest.xml +25 -0
  9. package/android/app/src/main/java/com/anonymous/reactnativeuniversalkeyboardawarescrollview/MainActivity.kt +61 -0
  10. package/android/app/src/main/java/com/anonymous/reactnativeuniversalkeyboardawarescrollview/MainApplication.kt +56 -0
  11. package/android/app/src/main/res/drawable/ic_launcher_background.xml +6 -0
  12. package/android/app/src/main/res/drawable/rn_edit_text_material.xml +37 -0
  13. package/android/app/src/main/res/drawable-hdpi/splashscreen_logo.png +0 -0
  14. package/android/app/src/main/res/drawable-mdpi/splashscreen_logo.png +0 -0
  15. package/android/app/src/main/res/drawable-xhdpi/splashscreen_logo.png +0 -0
  16. package/android/app/src/main/res/drawable-xxhdpi/splashscreen_logo.png +0 -0
  17. package/android/app/src/main/res/drawable-xxxhdpi/splashscreen_logo.png +0 -0
  18. package/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +5 -0
  19. package/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +5 -0
  20. package/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp +0 -0
  21. package/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp +0 -0
  22. package/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp +0 -0
  23. package/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp +0 -0
  24. package/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp +0 -0
  25. package/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp +0 -0
  26. package/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp +0 -0
  27. package/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp +0 -0
  28. package/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp +0 -0
  29. package/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp +0 -0
  30. package/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp +0 -0
  31. package/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp +0 -0
  32. package/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp +0 -0
  33. package/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp +0 -0
  34. package/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp +0 -0
  35. package/android/app/src/main/res/values/colors.xml +6 -0
  36. package/android/app/src/main/res/values/strings.xml +5 -0
  37. package/android/app/src/main/res/values/styles.xml +11 -0
  38. package/android/app/src/main/res/values-night/colors.xml +1 -0
  39. package/android/build.gradle +89 -0
  40. package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  41. package/android/gradle/wrapper/gradle-wrapper.properties +7 -0
  42. package/android/gradle.properties +65 -0
  43. package/android/gradlew +251 -0
  44. package/android/gradlew.bat +94 -0
  45. package/android/settings.gradle +39 -0
  46. package/android/src/main/AndroidManifest.xml +3 -0
  47. package/android/src/main/java/com/universalkeyboard/UniversalKeyboardModule.kt +349 -0
  48. package/android/src/main/java/com/universalkeyboard/UniversalKeyboardPackage.kt +21 -0
  49. package/ios/.xcode.env +11 -0
  50. package/ios/Podfile +60 -0
  51. package/ios/Podfile.lock +2001 -0
  52. package/ios/Podfile.properties.json +5 -0
  53. package/ios/UniversalKeyboard.h +24 -0
  54. package/ios/UniversalKeyboard.m +413 -0
  55. package/ios/reactnativeuniversalkeyboardawarescrollview/AppDelegate.swift +70 -0
  56. package/ios/reactnativeuniversalkeyboardawarescrollview/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png +0 -0
  57. package/ios/reactnativeuniversalkeyboardawarescrollview/Images.xcassets/AppIcon.appiconset/Contents.json +14 -0
  58. package/ios/reactnativeuniversalkeyboardawarescrollview/Images.xcassets/Contents.json +6 -0
  59. package/ios/reactnativeuniversalkeyboardawarescrollview/Images.xcassets/SplashScreenBackground.colorset/Contents.json +20 -0
  60. package/ios/reactnativeuniversalkeyboardawarescrollview/Images.xcassets/SplashScreenLegacy.imageset/Contents.json +23 -0
  61. package/ios/reactnativeuniversalkeyboardawarescrollview/Images.xcassets/SplashScreenLegacy.imageset/image.png +0 -0
  62. package/ios/reactnativeuniversalkeyboardawarescrollview/Images.xcassets/SplashScreenLegacy.imageset/image@2x.png +0 -0
  63. package/ios/reactnativeuniversalkeyboardawarescrollview/Images.xcassets/SplashScreenLegacy.imageset/image@3x.png +0 -0
  64. package/ios/reactnativeuniversalkeyboardawarescrollview/Info.plist +76 -0
  65. package/ios/reactnativeuniversalkeyboardawarescrollview/PrivacyInfo.xcprivacy +48 -0
  66. package/ios/reactnativeuniversalkeyboardawarescrollview/SplashScreen.storyboard +48 -0
  67. package/ios/reactnativeuniversalkeyboardawarescrollview/Supporting/Expo.plist +12 -0
  68. package/ios/reactnativeuniversalkeyboardawarescrollview/reactnativeuniversalkeyboardawarescrollview-Bridging-Header.h +3 -0
  69. package/ios/reactnativeuniversalkeyboardawarescrollview/reactnativeuniversalkeyboardawarescrollview.entitlements +5 -0
  70. package/ios/reactnativeuniversalkeyboardawarescrollview.xcodeproj/project.pbxproj +540 -0
  71. package/ios/reactnativeuniversalkeyboardawarescrollview.xcodeproj/xcshareddata/xcschemes/reactnativeuniversalkeyboardawarescrollview.xcscheme +88 -0
  72. package/ios/reactnativeuniversalkeyboardawarescrollview.xcworkspace/contents.xcworkspacedata +10 -0
  73. package/package.json +61 -0
  74. package/react-native-universal-keyboard-aware-scrollview.podspec +32 -0
  75. package/react-native.config.js +18 -0
  76. package/src/NativeModule.ts +61 -0
  77. package/src/components/KeyboardAwareScrollView.tsx +388 -0
  78. package/src/components/index.ts +5 -0
  79. package/src/hooks/index.ts +2 -0
  80. package/src/hooks/useKeyboard.ts +360 -0
  81. package/src/index.ts +27 -0
  82. package/src/types.ts +87 -0
  83. package/src/utils/KeyboardController.ts +112 -0
  84. package/src/utils/index.ts +1 -0
@@ -0,0 +1,360 @@
1
+ import { useEffect, useState, useCallback, useRef } from 'react';
2
+ import { Platform, Keyboard, LayoutAnimation, UIManager } from 'react-native';
3
+ import {
4
+ UniversalKeyboardModule,
5
+ KeyboardEventEmitter,
6
+ isNativeModuleAvailable,
7
+ } from '../NativeModule';
8
+ import type { KeyboardEvent } from '../types';
9
+
10
+ // Enable LayoutAnimation on Android
11
+ if (
12
+ Platform.OS === 'android' &&
13
+ UIManager.setLayoutAnimationEnabledExperimental
14
+ ) {
15
+ UIManager.setLayoutAnimationEnabledExperimental(true);
16
+ }
17
+
18
+ /**
19
+ * Options for useKeyboard hook
20
+ */
21
+ export interface UseKeyboardOptions {
22
+ /** Enable keyboard handling on Android (default: true) */
23
+ enableOnAndroid?: boolean;
24
+ /** Enable keyboard handling on iOS (default: true) */
25
+ enableOnIOS?: boolean;
26
+ /** Whether to use native module events (default: true on both platforms) */
27
+ useNativeEvents?: boolean;
28
+ /** Whether to animate height changes (default: true) */
29
+ animated?: boolean;
30
+ /** Callback when keyboard will show (iOS only) */
31
+ onKeyboardWillShow?: (event: KeyboardEvent) => void;
32
+ /** Callback when keyboard will hide (iOS only) */
33
+ onKeyboardWillHide?: (event: KeyboardEvent) => void;
34
+ /** Callback when keyboard did show */
35
+ onKeyboardDidShow?: (event: KeyboardEvent) => void;
36
+ /** Callback when keyboard did hide */
37
+ onKeyboardDidHide?: (event: KeyboardEvent) => void;
38
+ /** Callback when keyboard height changes */
39
+ onKeyboardHeightChange?: (height: number) => void;
40
+ }
41
+
42
+ /**
43
+ * Return type for useKeyboard hook
44
+ */
45
+ export interface UseKeyboardReturn {
46
+ /** Current keyboard height in points/dp */
47
+ keyboardHeight: number;
48
+ /** Whether the keyboard is currently visible */
49
+ isKeyboardVisible: boolean;
50
+ /** Whether the keyboard is currently animating */
51
+ isAnimating: boolean;
52
+ /** Dismiss the keyboard programmatically */
53
+ dismissKeyboard: () => Promise<void>;
54
+ /** Screen height in points/dp */
55
+ screenHeight: number;
56
+ /** Animation duration in ms when keyboard shows/hides */
57
+ animationDuration: number;
58
+ /** Safe area bottom inset (iOS only) */
59
+ safeAreaBottom: number;
60
+ }
61
+
62
+ /**
63
+ * useKeyboard - A hook for tracking keyboard state and height
64
+ *
65
+ * This hook provides reliable keyboard detection that works in:
66
+ * - Normal React Native screens
67
+ * - React Native Modal components
68
+ * - BottomSheet components
69
+ * - Any overlay/presentation scenarios
70
+ *
71
+ * @example
72
+ * ```tsx
73
+ * const { keyboardHeight, isKeyboardVisible, dismissKeyboard } = useKeyboard();
74
+ *
75
+ * return (
76
+ * <View style={{ paddingBottom: keyboardHeight }}>
77
+ * <TextInput placeholder="Type here..." />
78
+ * <Button onPress={dismissKeyboard} title="Dismiss" />
79
+ * </View>
80
+ * );
81
+ * ```
82
+ */
83
+ export function useKeyboard(options: UseKeyboardOptions = {}): UseKeyboardReturn {
84
+ const {
85
+ enableOnAndroid = true,
86
+ enableOnIOS = true,
87
+ useNativeEvents = true,
88
+ animated = true,
89
+ onKeyboardWillShow,
90
+ onKeyboardWillHide,
91
+ onKeyboardDidShow,
92
+ onKeyboardDidHide,
93
+ onKeyboardHeightChange,
94
+ } = options;
95
+
96
+ const [keyboardState, setKeyboardState] = useState({
97
+ keyboardHeight: 0,
98
+ isKeyboardVisible: false,
99
+ isAnimating: false,
100
+ screenHeight: 0,
101
+ animationDuration: 0,
102
+ safeAreaBottom: 0,
103
+ });
104
+
105
+ const isEnabled =
106
+ (Platform.OS === 'android' && enableOnAndroid) ||
107
+ (Platform.OS === 'ios' && enableOnIOS);
108
+
109
+ const nativeModuleActive = useRef(false);
110
+
111
+ // Handle keyboard height change
112
+ const handleKeyboardHeightChange = useCallback(
113
+ (event: KeyboardEvent) => {
114
+ if (!isEnabled) return;
115
+
116
+ const height = event.height ?? 0;
117
+ const duration = event.duration ?? 250;
118
+
119
+ if (animated && duration > 0) {
120
+ LayoutAnimation.configureNext({
121
+ duration: duration,
122
+ update: {
123
+ type: LayoutAnimation.Types.keyboard,
124
+ property: LayoutAnimation.Properties.opacity,
125
+ },
126
+ });
127
+ }
128
+
129
+ setKeyboardState((prev) => ({
130
+ ...prev,
131
+ keyboardHeight: height,
132
+ isKeyboardVisible: height > 0,
133
+ screenHeight: event.screenHeight ?? prev.screenHeight,
134
+ animationDuration: duration,
135
+ safeAreaBottom: event.safeAreaBottom ?? prev.safeAreaBottom,
136
+ isAnimating: false,
137
+ }));
138
+
139
+ onKeyboardHeightChange?.(height);
140
+ },
141
+ [isEnabled, animated, onKeyboardHeightChange]
142
+ );
143
+
144
+ // Handle keyboard will show
145
+ const handleKeyboardWillShow = useCallback(
146
+ (event: KeyboardEvent) => {
147
+ if (!isEnabled) return;
148
+
149
+ setKeyboardState((prev) => ({
150
+ ...prev,
151
+ isAnimating: true,
152
+ }));
153
+
154
+ onKeyboardWillShow?.(event);
155
+ },
156
+ [isEnabled, onKeyboardWillShow]
157
+ );
158
+
159
+ // Handle keyboard will hide
160
+ const handleKeyboardWillHide = useCallback(
161
+ (event: KeyboardEvent) => {
162
+ if (!isEnabled) return;
163
+
164
+ setKeyboardState((prev) => ({
165
+ ...prev,
166
+ isAnimating: true,
167
+ }));
168
+
169
+ onKeyboardWillHide?.(event);
170
+ },
171
+ [isEnabled, onKeyboardWillHide]
172
+ );
173
+
174
+ // Handle keyboard did show
175
+ const handleKeyboardDidShow = useCallback(
176
+ (event: KeyboardEvent) => {
177
+ if (!isEnabled) return;
178
+
179
+ setKeyboardState((prev) => ({
180
+ ...prev,
181
+ keyboardHeight: event.height,
182
+ isKeyboardVisible: true,
183
+ isAnimating: false,
184
+ screenHeight: event.screenHeight ?? prev.screenHeight,
185
+ safeAreaBottom: event.safeAreaBottom ?? prev.safeAreaBottom,
186
+ }));
187
+
188
+ onKeyboardDidShow?.(event);
189
+ },
190
+ [isEnabled, onKeyboardDidShow]
191
+ );
192
+
193
+ // Handle keyboard did hide
194
+ const handleKeyboardDidHide = useCallback(
195
+ (event: KeyboardEvent) => {
196
+ if (!isEnabled) return;
197
+
198
+ setKeyboardState((prev) => ({
199
+ ...prev,
200
+ keyboardHeight: 0,
201
+ isKeyboardVisible: false,
202
+ isAnimating: false,
203
+ }));
204
+
205
+ onKeyboardDidHide?.(event);
206
+ },
207
+ [isEnabled, onKeyboardDidHide]
208
+ );
209
+
210
+ // Dismiss keyboard
211
+ const dismissKeyboard = useCallback(async () => {
212
+ if (isNativeModuleAvailable()) {
213
+ try {
214
+ await UniversalKeyboardModule.dismissKeyboard();
215
+ } catch {
216
+ // Fall back to RN Keyboard API
217
+ Keyboard.dismiss();
218
+ }
219
+ } else {
220
+ Keyboard.dismiss();
221
+ }
222
+ }, []);
223
+
224
+ // Set up event listeners
225
+ useEffect(() => {
226
+ if (!isEnabled) return;
227
+
228
+ const subscriptions: (() => void)[] = [];
229
+
230
+ // Use native module events if available and enabled
231
+ if (useNativeEvents && isNativeModuleAvailable() && KeyboardEventEmitter) {
232
+ // Start listening on native side
233
+ UniversalKeyboardModule.startListening()
234
+ .then(() => {
235
+ nativeModuleActive.current = true;
236
+ })
237
+ .catch((error) => {
238
+ console.warn('[useKeyboard] Failed to start native listener:', error);
239
+ });
240
+
241
+ // Subscribe to native events
242
+ subscriptions.push(
243
+ (() => {
244
+ const sub = KeyboardEventEmitter.addListener(
245
+ 'keyboardHeightChanged',
246
+ handleKeyboardHeightChange
247
+ );
248
+ return () => sub.remove();
249
+ })()
250
+ );
251
+
252
+ subscriptions.push(
253
+ (() => {
254
+ const sub = KeyboardEventEmitter.addListener(
255
+ 'keyboardDidShow',
256
+ handleKeyboardDidShow
257
+ );
258
+ return () => sub.remove();
259
+ })()
260
+ );
261
+
262
+ subscriptions.push(
263
+ (() => {
264
+ const sub = KeyboardEventEmitter.addListener(
265
+ 'keyboardDidHide',
266
+ handleKeyboardDidHide
267
+ );
268
+ return () => sub.remove();
269
+ })()
270
+ );
271
+
272
+ // iOS only events
273
+ if (Platform.OS === 'ios') {
274
+ subscriptions.push(
275
+ (() => {
276
+ const sub = KeyboardEventEmitter.addListener(
277
+ 'keyboardWillShow',
278
+ handleKeyboardWillShow
279
+ );
280
+ return () => sub.remove();
281
+ })()
282
+ );
283
+
284
+ subscriptions.push(
285
+ (() => {
286
+ const sub = KeyboardEventEmitter.addListener(
287
+ 'keyboardWillHide',
288
+ handleKeyboardWillHide
289
+ );
290
+ return () => sub.remove();
291
+ })()
292
+ );
293
+ }
294
+ } else {
295
+ // Fall back to React Native Keyboard API
296
+ const mapRNKeyboardEvent = (e: any): KeyboardEvent => ({
297
+ height: e.endCoordinates?.height ?? 0,
298
+ isVisible: true,
299
+ duration: e.duration ? e.duration * 1000 : 250,
300
+ screenHeight: e.endCoordinates?.screenY ?? 0,
301
+ timestamp: Date.now(),
302
+ endCoordinates: e.endCoordinates,
303
+ startCoordinates: e.startCoordinates,
304
+ });
305
+
306
+ const showSub = Keyboard.addListener('keyboardDidShow', (e) =>
307
+ handleKeyboardDidShow(mapRNKeyboardEvent(e))
308
+ );
309
+ const hideSub = Keyboard.addListener('keyboardDidHide', (e) =>
310
+ handleKeyboardDidHide({
311
+ height: 0,
312
+ isVisible: false,
313
+ timestamp: Date.now(),
314
+ })
315
+ );
316
+
317
+ subscriptions.push(() => showSub.remove());
318
+ subscriptions.push(() => hideSub.remove());
319
+
320
+ if (Platform.OS === 'ios') {
321
+ const willShowSub = Keyboard.addListener('keyboardWillShow', (e) =>
322
+ handleKeyboardWillShow(mapRNKeyboardEvent(e))
323
+ );
324
+ const willHideSub = Keyboard.addListener('keyboardWillHide', (e) =>
325
+ handleKeyboardWillHide({
326
+ height: 0,
327
+ isVisible: false,
328
+ timestamp: Date.now(),
329
+ })
330
+ );
331
+
332
+ subscriptions.push(() => willShowSub.remove());
333
+ subscriptions.push(() => willHideSub.remove());
334
+ }
335
+ }
336
+
337
+ // Cleanup
338
+ return () => {
339
+ subscriptions.forEach((unsub) => unsub());
340
+
341
+ if (nativeModuleActive.current) {
342
+ UniversalKeyboardModule.stopListening().catch(() => {});
343
+ nativeModuleActive.current = false;
344
+ }
345
+ };
346
+ }, [
347
+ isEnabled,
348
+ useNativeEvents,
349
+ handleKeyboardHeightChange,
350
+ handleKeyboardWillShow,
351
+ handleKeyboardWillHide,
352
+ handleKeyboardDidShow,
353
+ handleKeyboardDidHide,
354
+ ]);
355
+
356
+ return {
357
+ ...keyboardState,
358
+ dismissKeyboard,
359
+ };
360
+ }
package/src/index.ts ADDED
@@ -0,0 +1,27 @@
1
+ /**
2
+ * react-native-universal-keyboard-aware-scrollview
3
+ *
4
+ * A universal keyboard-aware ScrollView for React Native that works correctly
5
+ * in normal screens, modals, and bottom sheets on both Android and iOS.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+
10
+ // Native module and event types
11
+ export { UniversalKeyboardModule } from './NativeModule';
12
+ export type {
13
+ KeyboardEvent,
14
+ KeyboardEventName,
15
+ KeyboardCoordinates,
16
+ } from './types';
17
+
18
+ // Hook
19
+ export { useKeyboard } from './hooks/useKeyboard';
20
+ export type { UseKeyboardOptions, UseKeyboardReturn } from './hooks/useKeyboard';
21
+
22
+ // Components
23
+ export { KeyboardAwareScrollView } from './components/KeyboardAwareScrollView';
24
+ export type { KeyboardAwareScrollViewProps } from './components/KeyboardAwareScrollView';
25
+
26
+ // Utilities
27
+ export { KeyboardController } from './utils/KeyboardController';
package/src/types.ts ADDED
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Type definitions for the Universal Keyboard module
3
+ */
4
+
5
+ /**
6
+ * Coordinates describing the keyboard frame
7
+ */
8
+ export interface KeyboardCoordinates {
9
+ /** X position of the keyboard on screen */
10
+ screenX: number;
11
+ /** Y position of the keyboard on screen */
12
+ screenY: number;
13
+ /** Width of the keyboard */
14
+ width: number;
15
+ /** Height of the keyboard */
16
+ height: number;
17
+ }
18
+
19
+ /**
20
+ * Event data emitted for keyboard state changes
21
+ */
22
+ export interface KeyboardEvent {
23
+ /** Current keyboard height in points/dp */
24
+ height: number;
25
+ /** Whether the keyboard is currently visible */
26
+ isVisible: boolean;
27
+ /** Animation duration in milliseconds (iOS only) */
28
+ duration?: number;
29
+ /** Animation easing curve (iOS only) */
30
+ easing?: string;
31
+ /** Screen height in points/dp */
32
+ screenHeight?: number;
33
+ /** Screen width in points/dp (iOS only) */
34
+ screenWidth?: number;
35
+ /** Safe area bottom inset (iOS only, for notch devices) */
36
+ safeAreaBottom?: number;
37
+ /** End coordinates of the keyboard frame (iOS only) */
38
+ endCoordinates?: KeyboardCoordinates;
39
+ /** Start coordinates of the keyboard frame (iOS only) */
40
+ startCoordinates?: KeyboardCoordinates;
41
+ /** Timestamp of the event */
42
+ timestamp: number;
43
+ }
44
+
45
+ /**
46
+ * Names of keyboard events emitted by the native module
47
+ */
48
+ export type KeyboardEventName =
49
+ | 'keyboardDidShow'
50
+ | 'keyboardDidHide'
51
+ | 'keyboardHeightChanged'
52
+ | 'keyboardWillShow'
53
+ | 'keyboardWillHide';
54
+
55
+ /**
56
+ * Native module interface
57
+ */
58
+ export interface UniversalKeyboardNativeModule {
59
+ /** Start listening for keyboard events */
60
+ startListening(): Promise<boolean>;
61
+ /** Stop listening for keyboard events */
62
+ stopListening(): Promise<boolean>;
63
+ /** Get the current keyboard height */
64
+ getKeyboardHeight(): Promise<number>;
65
+ /** Check if the keyboard is currently visible */
66
+ isKeyboardVisible(): Promise<boolean>;
67
+ /** Dismiss the keyboard */
68
+ dismissKeyboard(): Promise<boolean>;
69
+ /** Add listener (required by RN EventEmitter) */
70
+ addListener(eventName: string): void;
71
+ /** Remove listeners (required by RN EventEmitter) */
72
+ removeListeners(count: number): void;
73
+ }
74
+
75
+ /**
76
+ * Options for keyboard behavior
77
+ */
78
+ export interface KeyboardOptions {
79
+ /** Enable keyboard handling on Android */
80
+ enableOnAndroid?: boolean;
81
+ /** Enable keyboard handling on iOS */
82
+ enableOnIOS?: boolean;
83
+ /** Extra space to add above the keyboard */
84
+ extraOffset?: number;
85
+ /** Behavior when keyboard appears: 'padding', 'position', or 'height' */
86
+ behavior?: 'padding' | 'position' | 'height';
87
+ }
@@ -0,0 +1,112 @@
1
+ import { Keyboard, Platform } from 'react-native';
2
+ import {
3
+ UniversalKeyboardModule,
4
+ isNativeModuleAvailable,
5
+ } from '../NativeModule';
6
+
7
+ /**
8
+ * KeyboardController - Utility class for programmatic keyboard control
9
+ *
10
+ * Provides static methods for dismissing the keyboard and querying keyboard state.
11
+ * Uses native module when available for more reliable behavior in modals.
12
+ */
13
+ export class KeyboardController {
14
+ /**
15
+ * Dismiss the keyboard programmatically
16
+ *
17
+ * Uses native implementation when available for better reliability,
18
+ * especially in modal contexts.
19
+ */
20
+ static async dismiss(): Promise<void> {
21
+ if (isNativeModuleAvailable()) {
22
+ try {
23
+ await UniversalKeyboardModule.dismissKeyboard();
24
+ return;
25
+ } catch (error) {
26
+ // Fall through to RN Keyboard
27
+ }
28
+ }
29
+ Keyboard.dismiss();
30
+ }
31
+
32
+ /**
33
+ * Get the current keyboard height
34
+ *
35
+ * @returns Keyboard height in points/dp, or 0 if keyboard is hidden
36
+ */
37
+ static async getHeight(): Promise<number> {
38
+ if (isNativeModuleAvailable()) {
39
+ try {
40
+ return await UniversalKeyboardModule.getKeyboardHeight();
41
+ } catch (error) {
42
+ return 0;
43
+ }
44
+ }
45
+ return 0;
46
+ }
47
+
48
+ /**
49
+ * Check if the keyboard is currently visible
50
+ *
51
+ * @returns true if keyboard is visible, false otherwise
52
+ */
53
+ static async isVisible(): Promise<boolean> {
54
+ if (isNativeModuleAvailable()) {
55
+ try {
56
+ return await UniversalKeyboardModule.isKeyboardVisible();
57
+ } catch (error) {
58
+ return false;
59
+ }
60
+ }
61
+ return false;
62
+ }
63
+
64
+ /**
65
+ * Start listening for keyboard events on the native side
66
+ *
67
+ * Call this if you want to use the native event emitter directly.
68
+ * Usually not needed as useKeyboard hook handles this automatically.
69
+ */
70
+ static async startListening(): Promise<boolean> {
71
+ if (isNativeModuleAvailable()) {
72
+ try {
73
+ return await UniversalKeyboardModule.startListening();
74
+ } catch (error) {
75
+ return false;
76
+ }
77
+ }
78
+ return false;
79
+ }
80
+
81
+ /**
82
+ * Stop listening for keyboard events on the native side
83
+ *
84
+ * Call this to clean up native listeners if you called startListening directly.
85
+ */
86
+ static async stopListening(): Promise<boolean> {
87
+ if (isNativeModuleAvailable()) {
88
+ try {
89
+ return await UniversalKeyboardModule.stopListening();
90
+ } catch (error) {
91
+ return false;
92
+ }
93
+ }
94
+ return false;
95
+ }
96
+
97
+ /**
98
+ * Check if the current platform is supported
99
+ */
100
+ static isSupported(): boolean {
101
+ return Platform.OS === 'ios' || Platform.OS === 'android';
102
+ }
103
+
104
+ /**
105
+ * Check if native module is available
106
+ */
107
+ static isNativeModuleAvailable(): boolean {
108
+ return isNativeModuleAvailable();
109
+ }
110
+ }
111
+
112
+ export default KeyboardController;
@@ -0,0 +1 @@
1
+ export { KeyboardController } from './KeyboardController';