react-native-universal-keyboard-aware-scrollview 1.0.2 → 1.0.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.
@@ -1,2 +1,6 @@
1
1
  export { useKeyboard } from './useKeyboard';
2
- export type { UseKeyboardOptions, UseKeyboardReturn } from './useKeyboard';
2
+ export type {
3
+ UseKeyboardOptions,
4
+ UseKeyboardReturn,
5
+ KeyboardEventData,
6
+ } from './useKeyboard';
@@ -1,365 +1,125 @@
1
1
  import { useEffect, useState, useCallback, useRef } from 'react';
2
- import { Platform, Keyboard, LayoutAnimation, UIManager, Dimensions } from 'react-native';
3
- import {
4
- UniversalKeyboardModule,
5
- KeyboardEventEmitter,
6
- isNativeModuleAvailable,
7
- } from '../NativeModule';
8
- import type { KeyboardEvent } from '../types';
2
+ import { Platform, Keyboard, Dimensions, EmitterSubscription } from 'react-native';
9
3
 
10
- // Enable LayoutAnimation on Android
11
- if (
12
- Platform.OS === 'android' &&
13
- UIManager.setLayoutAnimationEnabledExperimental
14
- ) {
15
- UIManager.setLayoutAnimationEnabledExperimental(true);
4
+ /**
5
+ * Keyboard event data
6
+ */
7
+ export interface KeyboardEventData {
8
+ height: number;
9
+ duration: number;
16
10
  }
17
11
 
18
12
  /**
19
13
  * Options for useKeyboard hook
20
14
  */
21
15
  export interface UseKeyboardOptions {
22
- /** Enable keyboard handling on Android (default: true) */
16
+ /** Enable on Android (default: true) */
23
17
  enableOnAndroid?: boolean;
24
- /** Enable keyboard handling on iOS (default: true) */
18
+ /** Enable on iOS (default: true) */
25
19
  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
- interface KeyboardState {
43
- keyboardHeight: number;
44
- isKeyboardVisible: boolean;
45
- isAnimating: boolean;
46
- screenHeight: number;
47
- animationDuration: number;
48
- safeAreaBottom: number;
20
+ /** Callback when keyboard shows */
21
+ onKeyboardShow?: (data: KeyboardEventData) => void;
22
+ /** Callback when keyboard hides */
23
+ onKeyboardHide?: () => void;
49
24
  }
50
25
 
51
26
  /**
52
27
  * Return type for useKeyboard hook
53
28
  */
54
29
  export interface UseKeyboardReturn {
55
- /** Current keyboard height in points/dp */
30
+ /** Current keyboard height */
56
31
  keyboardHeight: number;
57
- /** Whether the keyboard is currently visible */
32
+ /** Is keyboard visible */
58
33
  isKeyboardVisible: boolean;
59
- /** Whether the keyboard is currently animating */
60
- isAnimating: boolean;
61
- /** Dismiss the keyboard programmatically */
62
- dismissKeyboard: () => Promise<void>;
63
- /** Screen height in points/dp */
34
+ /** Screen height */
64
35
  screenHeight: number;
65
- /** Animation duration in ms when keyboard shows/hides */
66
- animationDuration: number;
67
- /** Safe area bottom inset (iOS only) */
68
- safeAreaBottom: number;
36
+ /** Dismiss keyboard */
37
+ dismissKeyboard: () => void;
69
38
  }
70
39
 
71
40
  /**
72
- * useKeyboard - A hook for tracking keyboard state and height
41
+ * useKeyboard - A simple hook to track keyboard state
73
42
  *
74
- * This hook provides reliable keyboard detection that works in:
75
- * - Normal React Native screens
76
- * - React Native Modal components
77
- * - BottomSheet components
78
- * - Any overlay/presentation scenarios
79
- * - Multiline TextInput (textarea) fields
43
+ * @example
44
+ * ```tsx
45
+ * const { keyboardHeight, isKeyboardVisible, dismissKeyboard } = useKeyboard();
46
+ * ```
80
47
  */
81
48
  export function useKeyboard(options: UseKeyboardOptions = {}): UseKeyboardReturn {
82
49
  const {
83
50
  enableOnAndroid = true,
84
51
  enableOnIOS = true,
85
- useNativeEvents = true,
86
- animated = true,
87
- onKeyboardWillShow,
88
- onKeyboardWillHide,
89
- onKeyboardDidShow,
90
- onKeyboardDidHide,
91
- onKeyboardHeightChange,
52
+ onKeyboardShow,
53
+ onKeyboardHide,
92
54
  } = options;
93
55
 
94
- const { height: initialScreenHeight } = Dimensions.get('window');
95
-
96
- const [keyboardState, setKeyboardState] = useState<KeyboardState>({
97
- keyboardHeight: 0,
98
- isKeyboardVisible: false,
99
- isAnimating: false,
100
- screenHeight: initialScreenHeight,
101
- animationDuration: 0,
102
- safeAreaBottom: 0,
103
- });
56
+ const screenHeight = Dimensions.get('window').height;
57
+ const [keyboardHeight, setKeyboardHeight] = useState(0);
58
+ const [isKeyboardVisible, setIsKeyboardVisible] = useState(false);
59
+ const prevHeight = useRef(0);
104
60
 
105
61
  const isEnabled =
106
62
  (Platform.OS === 'android' && enableOnAndroid) ||
107
63
  (Platform.OS === 'ios' && enableOnIOS);
108
64
 
109
- const nativeModuleActive = useRef(false);
110
- const lastHeightRef = useRef(0);
111
-
112
- // Handle keyboard height change with debounce to prevent flicker
113
- const handleKeyboardHeightChange = useCallback(
114
- (event: KeyboardEvent) => {
65
+ const handleShow = useCallback(
66
+ (event: any) => {
115
67
  if (!isEnabled) return;
116
68
 
117
- const height = event.height ?? 0;
118
- const duration = event.duration ?? 250;
119
-
120
- // Prevent duplicate updates
121
- if (height === lastHeightRef.current) return;
122
- lastHeightRef.current = height;
69
+ const height = event.endCoordinates?.height || 0;
70
+ const duration = (event.duration || 0.25) * 1000;
123
71
 
124
- if (animated && duration > 0 && Platform.OS === 'ios') {
125
- LayoutAnimation.configureNext({
126
- duration: duration,
127
- update: {
128
- type: LayoutAnimation.Types.keyboard,
129
- property: LayoutAnimation.Properties.opacity,
130
- },
131
- });
72
+ if (height !== prevHeight.current) {
73
+ prevHeight.current = height;
74
+ setKeyboardHeight(height);
75
+ setIsKeyboardVisible(true);
76
+ onKeyboardShow?.({ height, duration });
132
77
  }
133
-
134
- setKeyboardState((prev: KeyboardState) => ({
135
- ...prev,
136
- keyboardHeight: height,
137
- isKeyboardVisible: height > 0,
138
- screenHeight: event.screenHeight ?? prev.screenHeight,
139
- animationDuration: duration,
140
- safeAreaBottom: event.safeAreaBottom ?? prev.safeAreaBottom,
141
- isAnimating: false,
142
- }));
143
-
144
- onKeyboardHeightChange?.(height);
145
- },
146
- [isEnabled, animated, onKeyboardHeightChange]
147
- );
148
-
149
- // Handle keyboard will show
150
- const handleKeyboardWillShow = useCallback(
151
- (event: KeyboardEvent) => {
152
- if (!isEnabled) return;
153
-
154
- setKeyboardState((prev: KeyboardState) => ({
155
- ...prev,
156
- isAnimating: true,
157
- }));
158
-
159
- onKeyboardWillShow?.(event);
160
78
  },
161
- [isEnabled, onKeyboardWillShow]
79
+ [isEnabled, onKeyboardShow]
162
80
  );
163
81
 
164
- // Handle keyboard will hide
165
- const handleKeyboardWillHide = useCallback(
166
- (event: KeyboardEvent) => {
167
- if (!isEnabled) return;
168
-
169
- setKeyboardState((prev: KeyboardState) => ({
170
- ...prev,
171
- isAnimating: true,
172
- }));
173
-
174
- onKeyboardWillHide?.(event);
175
- },
176
- [isEnabled, onKeyboardWillHide]
177
- );
178
-
179
- // Handle keyboard did show
180
- const handleKeyboardDidShow = useCallback(
181
- (event: KeyboardEvent) => {
182
- if (!isEnabled) return;
183
-
184
- const height = event.height ?? 0;
185
- lastHeightRef.current = height;
186
-
187
- setKeyboardState((prev: KeyboardState) => ({
188
- ...prev,
189
- keyboardHeight: height,
190
- isKeyboardVisible: true,
191
- isAnimating: false,
192
- screenHeight: event.screenHeight ?? prev.screenHeight,
193
- safeAreaBottom: event.safeAreaBottom ?? prev.safeAreaBottom,
194
- }));
195
-
196
- onKeyboardDidShow?.(event);
197
- },
198
- [isEnabled, onKeyboardDidShow]
199
- );
200
-
201
- // Handle keyboard did hide
202
- const handleKeyboardDidHide = useCallback(
203
- (event: KeyboardEvent) => {
204
- if (!isEnabled) return;
205
-
206
- lastHeightRef.current = 0;
207
-
208
- setKeyboardState((prev: KeyboardState) => ({
209
- ...prev,
210
- keyboardHeight: 0,
211
- isKeyboardVisible: false,
212
- isAnimating: false,
213
- }));
214
-
215
- onKeyboardDidHide?.(event);
216
- },
217
- [isEnabled, onKeyboardDidHide]
218
- );
82
+ const handleHide = useCallback(() => {
83
+ if (!isEnabled) return;
219
84
 
220
- // Dismiss keyboard
221
- const dismissKeyboard = useCallback(async () => {
222
- if (isNativeModuleAvailable()) {
223
- try {
224
- await UniversalKeyboardModule.dismissKeyboard();
225
- } catch {
226
- // Fall back to RN Keyboard API
227
- Keyboard.dismiss();
228
- }
229
- } else {
230
- Keyboard.dismiss();
85
+ if (prevHeight.current !== 0) {
86
+ prevHeight.current = 0;
87
+ setKeyboardHeight(0);
88
+ setIsKeyboardVisible(false);
89
+ onKeyboardHide?.();
231
90
  }
91
+ }, [isEnabled, onKeyboardHide]);
92
+
93
+ const dismissKeyboard = useCallback(() => {
94
+ Keyboard.dismiss();
232
95
  }, []);
233
96
 
234
- // Set up event listeners
235
97
  useEffect(() => {
236
98
  if (!isEnabled) return;
237
99
 
238
- const subscriptions: (() => void)[] = [];
239
-
240
- // Use native module events if available and enabled
241
- if (useNativeEvents && isNativeModuleAvailable() && KeyboardEventEmitter) {
242
- // Start listening on native side
243
- UniversalKeyboardModule.startListening()
244
- .then(() => {
245
- nativeModuleActive.current = true;
246
- })
247
- .catch((error: Error) => {
248
- console.warn('[useKeyboard] Failed to start native listener:', error);
249
- });
250
-
251
- // Subscribe to native events
252
- const heightSub = KeyboardEventEmitter.addListener(
253
- 'keyboardHeightChanged',
254
- handleKeyboardHeightChange
255
- );
256
- subscriptions.push(() => heightSub.remove());
257
-
258
- const didShowSub = KeyboardEventEmitter.addListener(
259
- 'keyboardDidShow',
260
- handleKeyboardDidShow
261
- );
262
- subscriptions.push(() => didShowSub.remove());
263
-
264
- const didHideSub = KeyboardEventEmitter.addListener(
265
- 'keyboardDidHide',
266
- handleKeyboardDidHide
267
- );
268
- subscriptions.push(() => didHideSub.remove());
100
+ const subs: EmitterSubscription[] = [];
269
101
 
270
- // iOS only events
271
- if (Platform.OS === 'ios') {
272
- const willShowSub = KeyboardEventEmitter.addListener(
273
- 'keyboardWillShow',
274
- handleKeyboardWillShow
275
- );
276
- subscriptions.push(() => willShowSub.remove());
277
-
278
- const willHideSub = KeyboardEventEmitter.addListener(
279
- 'keyboardWillHide',
280
- handleKeyboardWillHide
281
- );
282
- subscriptions.push(() => willHideSub.remove());
283
- }
102
+ if (Platform.OS === 'ios') {
103
+ subs.push(Keyboard.addListener('keyboardWillShow', handleShow));
104
+ subs.push(Keyboard.addListener('keyboardWillHide', handleHide));
105
+ subs.push(Keyboard.addListener('keyboardDidShow', handleShow));
106
+ subs.push(Keyboard.addListener('keyboardDidHide', handleHide));
284
107
  } else {
285
- // Fall back to React Native Keyboard API
286
- const mapRNKeyboardEvent = (e: any): KeyboardEvent => ({
287
- height: e.endCoordinates?.height ?? 0,
288
- isVisible: true,
289
- duration: e.duration ? e.duration * 1000 : 250,
290
- screenHeight: e.endCoordinates?.screenY ?? initialScreenHeight,
291
- timestamp: Date.now(),
292
- endCoordinates: e.endCoordinates,
293
- startCoordinates: e.startCoordinates,
294
- });
295
-
296
- const showSub = Keyboard.addListener('keyboardDidShow', (e: any) =>
297
- handleKeyboardDidShow(mapRNKeyboardEvent(e))
298
- );
299
- subscriptions.push(() => showSub.remove());
300
-
301
- const hideSub = Keyboard.addListener('keyboardDidHide', () =>
302
- handleKeyboardDidHide({
303
- height: 0,
304
- isVisible: false,
305
- timestamp: Date.now(),
306
- })
307
- );
308
- subscriptions.push(() => hideSub.remove());
309
-
310
- if (Platform.OS === 'ios') {
311
- const willShowSub = Keyboard.addListener('keyboardWillShow', (e: any) =>
312
- handleKeyboardWillShow(mapRNKeyboardEvent(e))
313
- );
314
- subscriptions.push(() => willShowSub.remove());
315
-
316
- const willHideSub = Keyboard.addListener('keyboardWillHide', () =>
317
- handleKeyboardWillHide({
318
- height: 0,
319
- isVisible: false,
320
- timestamp: Date.now(),
321
- })
322
- );
323
- subscriptions.push(() => willHideSub.remove());
324
- }
108
+ subs.push(Keyboard.addListener('keyboardDidShow', handleShow));
109
+ subs.push(Keyboard.addListener('keyboardDidHide', handleHide));
325
110
  }
326
111
 
327
- // Cleanup
328
- return () => {
329
- subscriptions.forEach((unsub) => unsub());
330
-
331
- if (nativeModuleActive.current) {
332
- UniversalKeyboardModule.stopListening().catch(() => {});
333
- nativeModuleActive.current = false;
334
- }
335
- };
336
- }, [
337
- isEnabled,
338
- useNativeEvents,
339
- initialScreenHeight,
340
- handleKeyboardHeightChange,
341
- handleKeyboardWillShow,
342
- handleKeyboardWillHide,
343
- handleKeyboardDidShow,
344
- handleKeyboardDidHide,
345
- ]);
346
-
347
- // Update screen height on dimension change
348
- useEffect(() => {
349
- const subscription = Dimensions.addEventListener('change', ({ window }) => {
350
- setKeyboardState((prev: KeyboardState) => ({
351
- ...prev,
352
- screenHeight: window.height,
353
- }));
354
- });
355
-
356
112
  return () => {
357
- subscription?.remove();
113
+ subs.forEach((s) => s.remove());
358
114
  };
359
- }, []);
115
+ }, [isEnabled, handleShow, handleHide]);
360
116
 
361
117
  return {
362
- ...keyboardState,
118
+ keyboardHeight,
119
+ isKeyboardVisible,
120
+ screenHeight,
363
121
  dismissKeyboard,
364
122
  };
365
123
  }
124
+
125
+ export default useKeyboard;
package/src/index.ts CHANGED
@@ -1,27 +1,24 @@
1
1
  /**
2
2
  * react-native-universal-keyboard-aware-scrollview
3
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
4
+ * A universal keyboard-aware ScrollView that works in normal screens,
5
+ * modals, and with multiline TextInputs.
8
6
  */
9
7
 
10
- // Native module and event types
11
- export { UniversalKeyboardModule } from './NativeModule';
8
+ // Main component
9
+ export { KeyboardAwareScrollView } from './components/KeyboardAwareScrollView';
12
10
  export type {
13
- KeyboardEvent,
14
- KeyboardEventName,
15
- KeyboardCoordinates,
16
- } from './types';
11
+ KeyboardAwareScrollViewProps,
12
+ KeyboardAwareScrollViewRef,
13
+ } from './components/KeyboardAwareScrollView';
17
14
 
18
15
  // Hook
19
16
  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';
17
+ export type {
18
+ UseKeyboardOptions,
19
+ UseKeyboardReturn,
20
+ KeyboardEventData,
21
+ } from './hooks/useKeyboard';
25
22
 
26
- // Utilities
23
+ // Utility
27
24
  export { KeyboardController } from './utils/KeyboardController';
package/src/types.ts CHANGED
@@ -1,87 +1,50 @@
1
1
  /**
2
- * Type definitions for the Universal Keyboard module
2
+ * Type definitions for keyboard events
3
3
  */
4
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
5
  export interface KeyboardEvent {
23
- /** Current keyboard height in points/dp */
6
+ /** Keyboard height in points/dp */
24
7
  height: number;
25
- /** Whether the keyboard is currently visible */
8
+ /** Whether keyboard is visible */
26
9
  isVisible: boolean;
27
- /** Animation duration in milliseconds (iOS only) */
10
+ /** Animation duration in milliseconds */
28
11
  duration?: number;
29
- /** Animation easing curve (iOS only) */
12
+ /** Animation easing */
30
13
  easing?: string;
31
- /** Screen height in points/dp */
14
+ /** Screen height */
32
15
  screenHeight?: number;
33
- /** Screen width in points/dp (iOS only) */
16
+ /** Screen width */
34
17
  screenWidth?: number;
35
- /** Safe area bottom inset (iOS only, for notch devices) */
18
+ /** Safe area bottom inset */
36
19
  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 */
20
+ /** End coordinates of keyboard */
21
+ endCoordinates?: {
22
+ screenX: number;
23
+ screenY: number;
24
+ width: number;
25
+ height: number;
26
+ };
27
+ /** Start coordinates of keyboard */
28
+ startCoordinates?: {
29
+ screenX: number;
30
+ screenY: number;
31
+ width: number;
32
+ height: number;
33
+ };
34
+ /** Event timestamp */
42
35
  timestamp: number;
43
36
  }
44
37
 
45
- /**
46
- * Names of keyboard events emitted by the native module
47
- */
48
38
  export type KeyboardEventName =
49
39
  | 'keyboardDidShow'
50
40
  | 'keyboardDidHide'
51
- | 'keyboardHeightChanged'
52
41
  | '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
- }
42
+ | 'keyboardWillHide'
43
+ | 'keyboardHeightChanged';
74
44
 
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';
45
+ export interface KeyboardCoordinates {
46
+ screenX: number;
47
+ screenY: number;
48
+ width: number;
49
+ height: number;
87
50
  }