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.
- package/LICENSE +21 -0
- package/README.md +387 -0
- package/android/app/build.gradle +182 -0
- package/android/app/debug.keystore +0 -0
- package/android/app/proguard-rules.pro +14 -0
- package/android/app/src/debug/AndroidManifest.xml +7 -0
- package/android/app/src/debugOptimized/AndroidManifest.xml +7 -0
- package/android/app/src/main/AndroidManifest.xml +25 -0
- package/android/app/src/main/java/com/anonymous/reactnativeuniversalkeyboardawarescrollview/MainActivity.kt +61 -0
- package/android/app/src/main/java/com/anonymous/reactnativeuniversalkeyboardawarescrollview/MainApplication.kt +56 -0
- package/android/app/src/main/res/drawable/ic_launcher_background.xml +6 -0
- package/android/app/src/main/res/drawable/rn_edit_text_material.xml +37 -0
- package/android/app/src/main/res/drawable-hdpi/splashscreen_logo.png +0 -0
- package/android/app/src/main/res/drawable-mdpi/splashscreen_logo.png +0 -0
- package/android/app/src/main/res/drawable-xhdpi/splashscreen_logo.png +0 -0
- package/android/app/src/main/res/drawable-xxhdpi/splashscreen_logo.png +0 -0
- package/android/app/src/main/res/drawable-xxxhdpi/splashscreen_logo.png +0 -0
- package/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +5 -0
- package/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +5 -0
- package/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp +0 -0
- package/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp +0 -0
- package/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp +0 -0
- package/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp +0 -0
- package/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp +0 -0
- package/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp +0 -0
- package/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp +0 -0
- package/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp +0 -0
- package/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp +0 -0
- package/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp +0 -0
- package/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp +0 -0
- package/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp +0 -0
- package/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp +0 -0
- package/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp +0 -0
- package/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp +0 -0
- package/android/app/src/main/res/values/colors.xml +6 -0
- package/android/app/src/main/res/values/strings.xml +5 -0
- package/android/app/src/main/res/values/styles.xml +11 -0
- package/android/app/src/main/res/values-night/colors.xml +1 -0
- package/android/build.gradle +89 -0
- package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/android/gradle/wrapper/gradle-wrapper.properties +7 -0
- package/android/gradle.properties +65 -0
- package/android/gradlew +251 -0
- package/android/gradlew.bat +94 -0
- package/android/settings.gradle +39 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/java/com/universalkeyboard/UniversalKeyboardModule.kt +349 -0
- package/android/src/main/java/com/universalkeyboard/UniversalKeyboardPackage.kt +21 -0
- package/ios/.xcode.env +11 -0
- package/ios/Podfile +60 -0
- package/ios/Podfile.lock +2001 -0
- package/ios/Podfile.properties.json +5 -0
- package/ios/UniversalKeyboard.h +24 -0
- package/ios/UniversalKeyboard.m +413 -0
- package/ios/reactnativeuniversalkeyboardawarescrollview/AppDelegate.swift +70 -0
- package/ios/reactnativeuniversalkeyboardawarescrollview/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png +0 -0
- package/ios/reactnativeuniversalkeyboardawarescrollview/Images.xcassets/AppIcon.appiconset/Contents.json +14 -0
- package/ios/reactnativeuniversalkeyboardawarescrollview/Images.xcassets/Contents.json +6 -0
- package/ios/reactnativeuniversalkeyboardawarescrollview/Images.xcassets/SplashScreenBackground.colorset/Contents.json +20 -0
- package/ios/reactnativeuniversalkeyboardawarescrollview/Images.xcassets/SplashScreenLegacy.imageset/Contents.json +23 -0
- package/ios/reactnativeuniversalkeyboardawarescrollview/Images.xcassets/SplashScreenLegacy.imageset/image.png +0 -0
- package/ios/reactnativeuniversalkeyboardawarescrollview/Images.xcassets/SplashScreenLegacy.imageset/image@2x.png +0 -0
- package/ios/reactnativeuniversalkeyboardawarescrollview/Images.xcassets/SplashScreenLegacy.imageset/image@3x.png +0 -0
- package/ios/reactnativeuniversalkeyboardawarescrollview/Info.plist +76 -0
- package/ios/reactnativeuniversalkeyboardawarescrollview/PrivacyInfo.xcprivacy +48 -0
- package/ios/reactnativeuniversalkeyboardawarescrollview/SplashScreen.storyboard +48 -0
- package/ios/reactnativeuniversalkeyboardawarescrollview/Supporting/Expo.plist +12 -0
- package/ios/reactnativeuniversalkeyboardawarescrollview/reactnativeuniversalkeyboardawarescrollview-Bridging-Header.h +3 -0
- package/ios/reactnativeuniversalkeyboardawarescrollview/reactnativeuniversalkeyboardawarescrollview.entitlements +5 -0
- package/ios/reactnativeuniversalkeyboardawarescrollview.xcodeproj/project.pbxproj +540 -0
- package/ios/reactnativeuniversalkeyboardawarescrollview.xcodeproj/xcshareddata/xcschemes/reactnativeuniversalkeyboardawarescrollview.xcscheme +88 -0
- package/ios/reactnativeuniversalkeyboardawarescrollview.xcworkspace/contents.xcworkspacedata +10 -0
- package/package.json +61 -0
- package/react-native-universal-keyboard-aware-scrollview.podspec +32 -0
- package/react-native.config.js +18 -0
- package/src/NativeModule.ts +61 -0
- package/src/components/KeyboardAwareScrollView.tsx +388 -0
- package/src/components/index.ts +5 -0
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useKeyboard.ts +360 -0
- package/src/index.ts +27 -0
- package/src/types.ts +87 -0
- package/src/utils/KeyboardController.ts +112 -0
- 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';
|