react-native-universal-keyboard-aware-scrollview 1.0.0 → 1.0.2
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/README.md +134 -242
- package/android/build.gradle +2 -17
- package/package.json +8 -14
- package/react-native-universal-keyboard-aware-scrollview.podspec +3 -12
- package/src/components/KeyboardAwareScrollView.tsx +193 -84
- package/src/hooks/useKeyboard.ts +78 -73
- package/android/app/build.gradle +0 -182
- package/android/app/debug.keystore +0 -0
- package/android/app/proguard-rules.pro +0 -14
- package/android/app/src/debug/AndroidManifest.xml +0 -7
- package/android/app/src/debugOptimized/AndroidManifest.xml +0 -7
- package/android/app/src/main/AndroidManifest.xml +0 -25
- package/android/app/src/main/java/com/anonymous/reactnativeuniversalkeyboardawarescrollview/MainActivity.kt +0 -61
- package/android/app/src/main/java/com/anonymous/reactnativeuniversalkeyboardawarescrollview/MainApplication.kt +0 -56
- package/android/app/src/main/res/drawable/ic_launcher_background.xml +0 -6
- package/android/app/src/main/res/drawable/rn_edit_text_material.xml +0 -37
- 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 +0 -5
- package/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +0 -5
- 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 +0 -6
- package/android/app/src/main/res/values/strings.xml +0 -5
- package/android/app/src/main/res/values/styles.xml +0 -11
- package/android/app/src/main/res/values-night/colors.xml +0 -1
- package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/android/gradle/wrapper/gradle-wrapper.properties +0 -7
- package/android/gradle.properties +0 -65
- package/android/gradlew +0 -251
- package/android/gradlew.bat +0 -94
- package/android/settings.gradle +0 -39
- package/ios/.xcode.env +0 -11
- package/ios/Podfile +0 -60
- package/ios/Podfile.lock +0 -2001
- package/ios/Podfile.properties.json +0 -5
- package/ios/reactnativeuniversalkeyboardawarescrollview/AppDelegate.swift +0 -70
- package/ios/reactnativeuniversalkeyboardawarescrollview/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png +0 -0
- package/ios/reactnativeuniversalkeyboardawarescrollview/Images.xcassets/AppIcon.appiconset/Contents.json +0 -14
- package/ios/reactnativeuniversalkeyboardawarescrollview/Images.xcassets/Contents.json +0 -6
- package/ios/reactnativeuniversalkeyboardawarescrollview/Images.xcassets/SplashScreenBackground.colorset/Contents.json +0 -20
- package/ios/reactnativeuniversalkeyboardawarescrollview/Images.xcassets/SplashScreenLegacy.imageset/Contents.json +0 -23
- 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 +0 -76
- package/ios/reactnativeuniversalkeyboardawarescrollview/PrivacyInfo.xcprivacy +0 -48
- package/ios/reactnativeuniversalkeyboardawarescrollview/SplashScreen.storyboard +0 -48
- package/ios/reactnativeuniversalkeyboardawarescrollview/Supporting/Expo.plist +0 -12
- package/ios/reactnativeuniversalkeyboardawarescrollview/reactnativeuniversalkeyboardawarescrollview-Bridging-Header.h +0 -3
- package/ios/reactnativeuniversalkeyboardawarescrollview/reactnativeuniversalkeyboardawarescrollview.entitlements +0 -5
- package/ios/reactnativeuniversalkeyboardawarescrollview.xcodeproj/project.pbxproj +0 -540
- package/ios/reactnativeuniversalkeyboardawarescrollview.xcodeproj/xcshareddata/xcschemes/reactnativeuniversalkeyboardawarescrollview.xcscheme +0 -88
- package/ios/reactnativeuniversalkeyboardawarescrollview.xcworkspace/contents.xcworkspacedata +0 -10
package/README.md
CHANGED
|
@@ -2,57 +2,43 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/react-native-universal-keyboard-aware-scrollview)
|
|
4
4
|
[](https://github.com/AetherTechDev/react-native-universal-keyboard-aware-scrollview/blob/main/LICENSE)
|
|
5
|
-
[](https://reactnative.dev/)
|
|
6
5
|
|
|
7
6
|
A **universal keyboard-aware ScrollView** for React Native that **works correctly** in:
|
|
8
7
|
- ✅ Normal screens
|
|
9
8
|
- ✅ React Native Modal
|
|
10
|
-
- ✅ BottomSheet components
|
|
9
|
+
- ✅ BottomSheet components
|
|
10
|
+
- ✅ Multiline TextInput (textarea)
|
|
11
11
|
- ✅ Any overlay or dialog scenario
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
## 🔥 Platform Support
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
|
20
|
-
|-------|----------------|--------------|
|
|
21
|
-
| Keyboard overlaps input in Modal | ❌ | ✅ |
|
|
22
|
-
| Glitchy animations on Android | ❌ | ✅ |
|
|
23
|
-
| Incorrect height inside BottomSheet | ❌ | ✅ |
|
|
24
|
-
| Race conditions with keyboard events | ❌ | ✅ |
|
|
25
|
-
| Inconsistent behavior iOS vs Android | ❌ | ✅ |
|
|
26
|
-
|
|
27
|
-
### How We Solve These Issues
|
|
28
|
-
|
|
29
|
-
1. **Native Keyboard Detection**: Instead of relying solely on React Native's JavaScript keyboard events, we use platform-native APIs:
|
|
30
|
-
- **iOS**: `UIKeyboardWillChangeFrameNotification` for accurate keyboard frame tracking
|
|
31
|
-
- **Android**: `ViewTreeObserver.OnGlobalLayoutListener` with visible window frame measurement
|
|
32
|
-
|
|
33
|
-
2. **Modal-Aware Architecture**: Our native implementations measure keyboard height relative to the actual visible window, not the root activity/view controller. This means keyboard detection works correctly even when views are presented modally.
|
|
34
|
-
|
|
35
|
-
3. **Unified Event System**: Events are emitted from native → JavaScript through a consistent interface, eliminating timing issues and race conditions.
|
|
15
|
+
| Platform | Supported |
|
|
16
|
+
|----------|-----------|
|
|
17
|
+
| React Native CLI | ✅ |
|
|
18
|
+
| Expo (Development Build) | ✅ |
|
|
19
|
+
| Expo Go | ❌ (requires native code) |
|
|
36
20
|
|
|
37
21
|
## 📦 Installation
|
|
38
22
|
|
|
39
23
|
```bash
|
|
40
|
-
# Using npm
|
|
41
24
|
npm install react-native-universal-keyboard-aware-scrollview
|
|
42
|
-
|
|
43
|
-
# Using yarn
|
|
44
|
-
yarn add react-native-universal-keyboard-aware-scrollview
|
|
45
25
|
```
|
|
46
26
|
|
|
47
27
|
### iOS Setup
|
|
48
|
-
|
|
49
28
|
```bash
|
|
50
29
|
cd ios && pod install
|
|
51
30
|
```
|
|
52
31
|
|
|
53
32
|
### Android Setup
|
|
33
|
+
No additional setup required - auto-linking handles everything.
|
|
54
34
|
|
|
55
|
-
|
|
35
|
+
### Expo (Development Build)
|
|
36
|
+
```bash
|
|
37
|
+
npx expo prebuild
|
|
38
|
+
npx expo run:ios # or run:android
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
> **Note:** This package uses native modules, so it won't work in Expo Go. Use a development build instead.
|
|
56
42
|
|
|
57
43
|
## 🚀 Quick Start
|
|
58
44
|
|
|
@@ -60,59 +46,57 @@ No additional setup required. The native module is automatically linked.
|
|
|
60
46
|
|
|
61
47
|
```tsx
|
|
62
48
|
import React from 'react';
|
|
63
|
-
import { TextInput,
|
|
49
|
+
import { TextInput, StyleSheet, SafeAreaView } from 'react-native';
|
|
64
50
|
import { KeyboardAwareScrollView } from 'react-native-universal-keyboard-aware-scrollview';
|
|
65
51
|
|
|
66
|
-
function
|
|
52
|
+
export default function App() {
|
|
67
53
|
return (
|
|
68
|
-
<
|
|
69
|
-
<
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
54
|
+
<SafeAreaView style={styles.container}>
|
|
55
|
+
<KeyboardAwareScrollView style={styles.scroll}>
|
|
56
|
+
<TextInput placeholder="Name" style={styles.input} />
|
|
57
|
+
<TextInput placeholder="Email" style={styles.input} />
|
|
58
|
+
<TextInput placeholder="Phone" style={styles.input} />
|
|
59
|
+
<TextInput
|
|
60
|
+
placeholder="Message"
|
|
61
|
+
style={[styles.input, styles.multiline]}
|
|
62
|
+
multiline
|
|
63
|
+
numberOfLines={5}
|
|
64
|
+
/>
|
|
65
|
+
</KeyboardAwareScrollView>
|
|
66
|
+
</SafeAreaView>
|
|
78
67
|
);
|
|
79
68
|
}
|
|
80
69
|
|
|
81
70
|
const styles = StyleSheet.create({
|
|
82
|
-
container: {
|
|
83
|
-
|
|
84
|
-
},
|
|
71
|
+
container: { flex: 1 },
|
|
72
|
+
scroll: { flex: 1, padding: 16 },
|
|
85
73
|
input: {
|
|
86
74
|
borderWidth: 1,
|
|
87
75
|
borderColor: '#ccc',
|
|
88
76
|
borderRadius: 8,
|
|
89
77
|
padding: 12,
|
|
90
78
|
marginBottom: 16,
|
|
91
|
-
marginHorizontal: 16,
|
|
92
|
-
},
|
|
93
|
-
multiline: {
|
|
94
|
-
height: 100,
|
|
95
|
-
textAlignVertical: 'top',
|
|
96
79
|
},
|
|
80
|
+
multiline: { height: 120, textAlignVertical: 'top' },
|
|
97
81
|
});
|
|
98
82
|
```
|
|
99
83
|
|
|
100
|
-
### Usage Inside Modal
|
|
84
|
+
### Usage Inside Modal (The Key Feature!)
|
|
101
85
|
|
|
102
86
|
```tsx
|
|
103
87
|
import React, { useState } from 'react';
|
|
104
|
-
import { Modal, TextInput, View, Button, StyleSheet } from 'react-native';
|
|
88
|
+
import { Modal, TextInput, View, Button, StyleSheet, SafeAreaView } from 'react-native';
|
|
105
89
|
import { KeyboardAwareScrollView } from 'react-native-universal-keyboard-aware-scrollview';
|
|
106
90
|
|
|
107
|
-
function
|
|
91
|
+
export default function ModalExample() {
|
|
108
92
|
const [visible, setVisible] = useState(false);
|
|
109
93
|
|
|
110
94
|
return (
|
|
111
95
|
<>
|
|
112
|
-
<Button title="Open
|
|
96
|
+
<Button title="Open Modal" onPress={() => setVisible(true)} />
|
|
113
97
|
|
|
114
98
|
<Modal visible={visible} animationType="slide">
|
|
115
|
-
<
|
|
99
|
+
<SafeAreaView style={styles.container}>
|
|
116
100
|
<KeyboardAwareScrollView
|
|
117
101
|
insideModal={true}
|
|
118
102
|
extraScrollHeight={20}
|
|
@@ -122,123 +106,111 @@ function ModalForm() {
|
|
|
122
106
|
<TextInput
|
|
123
107
|
placeholder="Message"
|
|
124
108
|
style={[styles.input, styles.multiline]}
|
|
125
|
-
multiline
|
|
109
|
+
multiline
|
|
110
|
+
numberOfLines={4}
|
|
126
111
|
/>
|
|
127
112
|
</KeyboardAwareScrollView>
|
|
128
|
-
|
|
129
113
|
<Button title="Close" onPress={() => setVisible(false)} />
|
|
130
|
-
</
|
|
114
|
+
</SafeAreaView>
|
|
131
115
|
</Modal>
|
|
132
116
|
</>
|
|
133
117
|
);
|
|
134
118
|
}
|
|
119
|
+
|
|
120
|
+
const styles = StyleSheet.create({
|
|
121
|
+
container: { flex: 1 },
|
|
122
|
+
input: {
|
|
123
|
+
borderWidth: 1,
|
|
124
|
+
borderColor: '#ccc',
|
|
125
|
+
borderRadius: 8,
|
|
126
|
+
padding: 12,
|
|
127
|
+
margin: 16,
|
|
128
|
+
},
|
|
129
|
+
multiline: { height: 100, textAlignVertical: 'top' },
|
|
130
|
+
});
|
|
135
131
|
```
|
|
136
132
|
|
|
137
|
-
###
|
|
133
|
+
### Multiline TextInput (Textarea) Support
|
|
134
|
+
|
|
135
|
+
The component automatically detects multiline TextInputs and provides extra scroll space:
|
|
136
|
+
|
|
137
|
+
```tsx
|
|
138
|
+
<KeyboardAwareScrollView
|
|
139
|
+
extraHeightForMultiline={100} // Extra space for multiline inputs
|
|
140
|
+
>
|
|
141
|
+
<TextInput
|
|
142
|
+
placeholder="Write your message..."
|
|
143
|
+
multiline
|
|
144
|
+
numberOfLines={6}
|
|
145
|
+
style={{ height: 150, textAlignVertical: 'top' }}
|
|
146
|
+
/>
|
|
147
|
+
</KeyboardAwareScrollView>
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Using the Hook
|
|
138
151
|
|
|
139
152
|
```tsx
|
|
140
|
-
import React from 'react';
|
|
141
|
-
import { View, TextInput, StyleSheet } from 'react-native';
|
|
142
153
|
import { useKeyboard } from 'react-native-universal-keyboard-aware-scrollview';
|
|
143
154
|
|
|
144
|
-
function
|
|
155
|
+
function MyComponent() {
|
|
145
156
|
const {
|
|
146
157
|
keyboardHeight,
|
|
147
158
|
isKeyboardVisible,
|
|
148
159
|
dismissKeyboard
|
|
149
|
-
} = useKeyboard(
|
|
150
|
-
onKeyboardDidShow: (event) => {
|
|
151
|
-
console.log('Keyboard shown with height:', event.height);
|
|
152
|
-
},
|
|
153
|
-
onKeyboardDidHide: () => {
|
|
154
|
-
console.log('Keyboard hidden');
|
|
155
|
-
},
|
|
156
|
-
});
|
|
160
|
+
} = useKeyboard();
|
|
157
161
|
|
|
158
162
|
return (
|
|
159
|
-
<View style={
|
|
160
|
-
<TextInput placeholder="Type here..."
|
|
163
|
+
<View style={{ paddingBottom: keyboardHeight }}>
|
|
164
|
+
<TextInput placeholder="Type here..." />
|
|
161
165
|
{isKeyboardVisible && (
|
|
162
|
-
<Button title="Dismiss
|
|
166
|
+
<Button title="Dismiss" onPress={dismissKeyboard} />
|
|
163
167
|
)}
|
|
164
168
|
</View>
|
|
165
169
|
);
|
|
166
170
|
}
|
|
167
171
|
```
|
|
168
172
|
|
|
169
|
-
## 📖 API
|
|
170
|
-
|
|
171
|
-
### `KeyboardAwareScrollView`
|
|
172
|
-
|
|
173
|
-
A ScrollView that automatically adjusts for the keyboard.
|
|
173
|
+
## 📖 API
|
|
174
174
|
|
|
175
|
-
|
|
175
|
+
### `KeyboardAwareScrollView` Props
|
|
176
176
|
|
|
177
177
|
| Prop | Type | Default | Description |
|
|
178
178
|
|------|------|---------|-------------|
|
|
179
|
-
| `enableOnAndroid` | `boolean` | `true` | Enable
|
|
180
|
-
| `enableOnIOS` | `boolean` | `true` | Enable
|
|
181
|
-
| `extraScrollHeight` | `number` | `20` | Extra scroll
|
|
182
|
-
| `extraHeight` | `number` | `75` | Extra space for focused
|
|
179
|
+
| `enableOnAndroid` | `boolean` | `true` | Enable on Android |
|
|
180
|
+
| `enableOnIOS` | `boolean` | `true` | Enable on iOS |
|
|
181
|
+
| `extraScrollHeight` | `number` | `20` | Extra scroll above keyboard |
|
|
182
|
+
| `extraHeight` | `number` | `75` | Extra space for focused input |
|
|
183
|
+
| `extraHeightForMultiline` | `number` | `100` | Extra space for multiline inputs |
|
|
184
|
+
| `insideModal` | `boolean` | `false` | **Set to true when inside a Modal** |
|
|
183
185
|
| `enableAnimation` | `boolean` | `true` | Animate height changes |
|
|
184
|
-
| `
|
|
185
|
-
| `enableAutoScrollToFocused` | `boolean` | `true` | Auto-scroll
|
|
186
|
-
| `resetScrollToCoords` | `{x, y} \| null` | `null` | Reset scroll position on keyboard hide |
|
|
187
|
-
| `keyboardShouldPersistTaps` | `string` | `'handled'` | Keyboard dismiss behavior |
|
|
188
|
-
| `insideModal` | `boolean` | `false` | Optimize for modal usage |
|
|
189
|
-
| `enableKeyboardSpacer` | `boolean` | `true` | Use spacer view approach |
|
|
190
|
-
| `useContentInset` | `boolean` | `true` (iOS) | Use content inset vs padding |
|
|
186
|
+
| `keyboardShouldPersistTaps` | `string` | `'handled'` | Tap behavior |
|
|
187
|
+
| `enableAutoScrollToFocused` | `boolean` | `true` | Auto-scroll to focused input |
|
|
191
188
|
|
|
192
|
-
|
|
189
|
+
### `useKeyboard` Hook
|
|
193
190
|
|
|
194
191
|
```tsx
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
//
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
scrollViewRef.current?.dismissKeyboard();
|
|
192
|
+
const {
|
|
193
|
+
keyboardHeight, // Current keyboard height (number)
|
|
194
|
+
isKeyboardVisible, // Is keyboard showing (boolean)
|
|
195
|
+
dismissKeyboard, // Function to dismiss keyboard
|
|
196
|
+
screenHeight, // Screen height
|
|
197
|
+
safeAreaBottom, // Safe area inset (iOS)
|
|
198
|
+
} = useKeyboard(options);
|
|
203
199
|
```
|
|
204
200
|
|
|
205
|
-
### `useKeyboard` Hook
|
|
206
|
-
|
|
207
|
-
A hook for tracking keyboard state.
|
|
208
|
-
|
|
209
201
|
#### Options
|
|
210
202
|
|
|
211
203
|
```tsx
|
|
212
|
-
|
|
213
|
-
enableOnAndroid: true,
|
|
214
|
-
enableOnIOS: true,
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
onKeyboardWillHide: (e) => {}, // iOS only
|
|
219
|
-
onKeyboardDidShow: (e) => {},
|
|
220
|
-
onKeyboardDidHide: (e) => {},
|
|
221
|
-
onKeyboardHeightChange: (h) => {},
|
|
204
|
+
useKeyboard({
|
|
205
|
+
enableOnAndroid: true,
|
|
206
|
+
enableOnIOS: true,
|
|
207
|
+
onKeyboardDidShow: (event) => {},
|
|
208
|
+
onKeyboardDidHide: (event) => {},
|
|
209
|
+
onKeyboardHeightChange: (height) => {},
|
|
222
210
|
});
|
|
223
211
|
```
|
|
224
212
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
```tsx
|
|
228
|
-
interface UseKeyboardReturn {
|
|
229
|
-
keyboardHeight: number; // Current keyboard height
|
|
230
|
-
isKeyboardVisible: boolean; // Is keyboard visible
|
|
231
|
-
isAnimating: boolean; // Is keyboard animating
|
|
232
|
-
dismissKeyboard: () => Promise<void>;
|
|
233
|
-
screenHeight: number; // Screen height
|
|
234
|
-
animationDuration: number; // Animation duration
|
|
235
|
-
safeAreaBottom: number; // Safe area inset (iOS)
|
|
236
|
-
}
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
### `KeyboardController`
|
|
240
|
-
|
|
241
|
-
Static utility class for keyboard control.
|
|
213
|
+
### `KeyboardController` Utility
|
|
242
214
|
|
|
243
215
|
```tsx
|
|
244
216
|
import { KeyboardController } from 'react-native-universal-keyboard-aware-scrollview';
|
|
@@ -246,142 +218,62 @@ import { KeyboardController } from 'react-native-universal-keyboard-aware-scroll
|
|
|
246
218
|
// Dismiss keyboard
|
|
247
219
|
await KeyboardController.dismiss();
|
|
248
220
|
|
|
249
|
-
// Get
|
|
221
|
+
// Get current height
|
|
250
222
|
const height = await KeyboardController.getHeight();
|
|
251
223
|
|
|
252
|
-
// Check
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
// Check if native module is available
|
|
256
|
-
const hasNative = KeyboardController.isNativeModuleAvailable();
|
|
224
|
+
// Check if visible
|
|
225
|
+
const visible = await KeyboardController.isVisible();
|
|
257
226
|
```
|
|
258
227
|
|
|
259
|
-
##
|
|
260
|
-
|
|
261
|
-
This package works with Expo, but **requires a development build** (not Expo Go).
|
|
262
|
-
|
|
263
|
-
### Expo Bare Workflow
|
|
264
|
-
|
|
265
|
-
Works out of the box. Install and use normally.
|
|
266
|
-
|
|
267
|
-
### Expo Managed Workflow
|
|
268
|
-
|
|
269
|
-
1. Install the package:
|
|
270
|
-
```bash
|
|
271
|
-
npx expo install react-native-universal-keyboard-aware-scrollview
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
2. Create a development build:
|
|
275
|
-
```bash
|
|
276
|
-
npx expo prebuild
|
|
277
|
-
npx expo run:ios # or run:android
|
|
278
|
-
```
|
|
279
|
-
|
|
280
|
-
3. Use in your app as normal.
|
|
281
|
-
|
|
282
|
-
> **Note**: This package contains native code, so it cannot run in Expo Go. You must use a development build or EAS Build.
|
|
283
|
-
|
|
284
|
-
## 🏗 Architecture
|
|
285
|
-
|
|
286
|
-
### Native Implementation Details
|
|
287
|
-
|
|
288
|
-
#### Android (Kotlin)
|
|
228
|
+
## 🎯 Why This Package?
|
|
289
229
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
│ getWindowVisibleDisplayFrame() │
|
|
298
|
-
│ ↓ │
|
|
299
|
-
│ Calculate keyboard height │
|
|
300
|
-
│ ↓ │
|
|
301
|
-
│ Emit via DeviceEventEmitter │
|
|
302
|
-
└─────────────────────────────────────┘
|
|
303
|
-
```
|
|
230
|
+
| Problem | Other Libraries | This Package |
|
|
231
|
+
|---------|----------------|--------------|
|
|
232
|
+
| Keyboard overlaps input in Modal | ❌ | ✅ |
|
|
233
|
+
| Multiline TextInput not working | ❌ | ✅ |
|
|
234
|
+
| Android resize glitches | ❌ | ✅ |
|
|
235
|
+
| Inconsistent heights | ❌ | ✅ |
|
|
236
|
+
| Race conditions | ❌ | ✅ |
|
|
304
237
|
|
|
305
|
-
|
|
238
|
+
### Technical Implementation
|
|
306
239
|
|
|
307
|
-
|
|
240
|
+
**iOS:** Uses `UIKeyboardWillChangeFrameNotification` for accurate keyboard frame detection.
|
|
308
241
|
|
|
309
|
-
|
|
242
|
+
**Android:** Uses `ViewTreeObserver.OnGlobalLayoutListener` to measure visible window frame.
|
|
310
243
|
|
|
311
|
-
|
|
312
|
-
┌─────────────────────────────────────┐
|
|
313
|
-
│ UniversalKeyboard │
|
|
314
|
-
├─────────────────────────────────────┤
|
|
315
|
-
│ NSNotificationCenter │
|
|
316
|
-
│ UIKeyboardWillChangeFrameNotification│
|
|
317
|
-
│ ↓ │
|
|
318
|
-
│ Extract keyboard frame from userInfo│
|
|
319
|
-
│ ↓ │
|
|
320
|
-
│ Calculate height from screen bottom │
|
|
321
|
-
│ ↓ │
|
|
322
|
-
│ Emit via RCTEventEmitter │
|
|
323
|
-
└─────────────────────────────────────┘
|
|
324
|
-
```
|
|
325
|
-
|
|
326
|
-
The iOS implementation uses `UIKeyboardWillChangeFrameNotification`, which provides the most accurate keyboard frame information. This notification fires for all keyboard changes including interactive dismiss gestures.
|
|
327
|
-
|
|
328
|
-
We calculate keyboard height as `screenHeight - keyboardFrame.origin.y`, which works correctly in all presentation contexts including modals.
|
|
244
|
+
Both approaches work correctly inside modals because they measure the actual visible area, not the root view.
|
|
329
245
|
|
|
330
246
|
## 🐛 Troubleshooting
|
|
331
247
|
|
|
332
|
-
### "Native module not found"
|
|
333
|
-
|
|
334
|
-
This usually means the native module isn't linked:
|
|
335
|
-
|
|
336
|
-
**iOS:**
|
|
337
|
-
```bash
|
|
338
|
-
cd ios && pod install --repo-update
|
|
339
|
-
```
|
|
248
|
+
### "Native module not found"
|
|
340
249
|
|
|
341
|
-
**Android:**
|
|
342
|
-
Clean and rebuild:
|
|
343
250
|
```bash
|
|
344
|
-
|
|
345
|
-
cd
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
### Keyboard still overlaps in Modal
|
|
251
|
+
# iOS
|
|
252
|
+
cd ios && pod install --repo-update && cd ..
|
|
349
253
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
```tsx
|
|
353
|
-
<KeyboardAwareScrollView insideModal={true}>
|
|
354
|
-
{/* content */}
|
|
355
|
-
</KeyboardAwareScrollView>
|
|
254
|
+
# Android - clean build
|
|
255
|
+
cd android && ./gradlew clean && cd ..
|
|
356
256
|
```
|
|
357
257
|
|
|
358
|
-
###
|
|
359
|
-
|
|
360
|
-
Try adjusting the animation settings:
|
|
258
|
+
### Expo Go not working
|
|
361
259
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
>
|
|
260
|
+
This package requires native code. Use:
|
|
261
|
+
```bash
|
|
262
|
+
npx expo prebuild
|
|
263
|
+
npx expo run:ios # or npx expo run:android
|
|
367
264
|
```
|
|
368
265
|
|
|
369
|
-
|
|
266
|
+
### Multiline TextInput not scrolling properly
|
|
370
267
|
|
|
268
|
+
Make sure you're using the `multiline` prop:
|
|
371
269
|
```tsx
|
|
372
|
-
<
|
|
270
|
+
<TextInput
|
|
271
|
+
multiline // Required!
|
|
272
|
+
numberOfLines={4}
|
|
273
|
+
style={{ height: 100 }}
|
|
274
|
+
/>
|
|
373
275
|
```
|
|
374
276
|
|
|
375
277
|
## 📄 License
|
|
376
278
|
|
|
377
279
|
MIT © [Vijay Kishan](https://github.com/AetherTechDev)
|
|
378
|
-
|
|
379
|
-
## 🤝 Contributing
|
|
380
|
-
|
|
381
|
-
Contributions are welcome! Please read our [contributing guidelines](CONTRIBUTING.md) first.
|
|
382
|
-
|
|
383
|
-
## 📞 Support
|
|
384
|
-
|
|
385
|
-
- 🐛 [Report bugs](https://github.com/AetherTechDev/react-native-universal-keyboard-aware-scrollview/issues)
|
|
386
|
-
- 💡 [Request features](https://github.com/AetherTechDev/react-native-universal-keyboard-aware-scrollview/issues)
|
|
387
|
-
- 📧 Email: vijay@aethertech.dev
|
package/android/build.gradle
CHANGED
|
@@ -14,10 +14,6 @@ buildscript {
|
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
def isNewArchitectureEnabled() {
|
|
18
|
-
return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
|
|
19
|
-
}
|
|
20
|
-
|
|
21
17
|
apply plugin: 'com.android.library'
|
|
22
18
|
apply plugin: 'kotlin-android'
|
|
23
19
|
|
|
@@ -32,14 +28,6 @@ android {
|
|
|
32
28
|
defaultConfig {
|
|
33
29
|
minSdkVersion safeExtGet('minSdkVersion', 21)
|
|
34
30
|
targetSdkVersion safeExtGet('targetSdkVersion', 34)
|
|
35
|
-
versionCode 1
|
|
36
|
-
versionName "1.0"
|
|
37
|
-
|
|
38
|
-
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
buildFeatures {
|
|
42
|
-
buildConfig true
|
|
43
31
|
}
|
|
44
32
|
|
|
45
33
|
buildTypes {
|
|
@@ -63,11 +51,7 @@ android {
|
|
|
63
51
|
|
|
64
52
|
sourceSets {
|
|
65
53
|
main {
|
|
66
|
-
|
|
67
|
-
java.srcDirs += ['src/newarch/java']
|
|
68
|
-
} else {
|
|
69
|
-
java.srcDirs += ['src/oldarch/java']
|
|
70
|
-
}
|
|
54
|
+
java.srcDirs = ['src/main/java']
|
|
71
55
|
}
|
|
72
56
|
}
|
|
73
57
|
}
|
|
@@ -85,5 +69,6 @@ repositories {
|
|
|
85
69
|
|
|
86
70
|
dependencies {
|
|
87
71
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:${safeExtGet('kotlinVersion', '1.9.10')}"
|
|
72
|
+
//noinspection GradleDynamicVersion
|
|
88
73
|
implementation "com.facebook.react:react-android:+"
|
|
89
74
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-universal-keyboard-aware-scrollview",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "A universal keyboard-aware ScrollView for React Native that works correctly in normal screens, modals, and bottom sheets on both Android and iOS",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -8,15 +8,17 @@
|
|
|
8
8
|
"source": "src/index.ts",
|
|
9
9
|
"files": [
|
|
10
10
|
"src",
|
|
11
|
-
"android",
|
|
12
|
-
"
|
|
11
|
+
"android/build.gradle",
|
|
12
|
+
"android/src",
|
|
13
|
+
"ios/UniversalKeyboard.h",
|
|
14
|
+
"ios/UniversalKeyboard.m",
|
|
13
15
|
"react-native.config.js",
|
|
14
16
|
"react-native-universal-keyboard-aware-scrollview.podspec",
|
|
15
|
-
"README.md"
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE"
|
|
16
19
|
],
|
|
17
20
|
"scripts": {
|
|
18
|
-
"typescript": "tsc --noEmit"
|
|
19
|
-
"lint": "eslint \"**/*.{js,ts,tsx}\""
|
|
21
|
+
"typescript": "tsc --noEmit"
|
|
20
22
|
},
|
|
21
23
|
"keywords": [
|
|
22
24
|
"react-native",
|
|
@@ -47,14 +49,6 @@
|
|
|
47
49
|
"react": ">=16.8.0",
|
|
48
50
|
"react-native": ">=0.60.0"
|
|
49
51
|
},
|
|
50
|
-
"peerDependenciesMeta": {
|
|
51
|
-
"react": {
|
|
52
|
-
"optional": false
|
|
53
|
-
},
|
|
54
|
-
"react-native": {
|
|
55
|
-
"optional": false
|
|
56
|
-
}
|
|
57
|
-
},
|
|
58
52
|
"engines": {
|
|
59
53
|
"node": ">=16"
|
|
60
54
|
}
|
|
@@ -13,20 +13,11 @@ Pod::Spec.new do |s|
|
|
|
13
13
|
DESC
|
|
14
14
|
s.homepage = package['homepage']
|
|
15
15
|
s.license = package['license']
|
|
16
|
-
s.author =
|
|
16
|
+
s.author = { "Vijay Kishan" => "vijay@aethertech.dev" }
|
|
17
17
|
s.platforms = { :ios => "12.0" }
|
|
18
|
-
s.source = { :git =>
|
|
18
|
+
s.source = { :git => "https://github.com/AetherTechDev/react-native-universal-keyboard-aware-scrollview.git", :tag => "v#{s.version}" }
|
|
19
19
|
|
|
20
|
-
s.source_files = "ios
|
|
21
|
-
|
|
22
|
-
# Use frameworks for Swift support
|
|
23
|
-
s.pod_target_xcconfig = {
|
|
24
|
-
'DEFINES_MODULE' => 'YES',
|
|
25
|
-
'SWIFT_OPTIMIZATION_LEVEL' => '-Onone'
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
# React Native dependency
|
|
29
|
-
install_modules_dependencies(s) if defined?(install_modules_dependencies)
|
|
20
|
+
s.source_files = "ios/*.{h,m}"
|
|
30
21
|
|
|
31
22
|
s.dependency "React-Core"
|
|
32
23
|
end
|