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.
package/README.md CHANGED
@@ -1,22 +1,16 @@
1
1
  # react-native-universal-keyboard-aware-scrollview
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/react-native-universal-keyboard-aware-scrollview.svg)](https://www.npmjs.com/package/react-native-universal-keyboard-aware-scrollview)
4
- [![license](https://img.shields.io/npm/l/react-native-universal-keyboard-aware-scrollview.svg)](https://github.com/AetherTechDev/react-native-universal-keyboard-aware-scrollview/blob/main/LICENSE)
5
4
 
6
- A **universal keyboard-aware ScrollView** for React Native that **works correctly** in:
7
- - ✅ Normal screens
8
- - ✅ React Native Modal
9
- - ✅ BottomSheet components
10
- - ✅ Multiline TextInput (textarea)
11
- - ✅ Any overlay or dialog scenario
5
+ A **universal keyboard-aware ScrollView** for React Native that **automatically scrolls** to keep the focused input visible when the keyboard appears.
12
6
 
13
- ## 🔥 Platform Support
7
+ ## Works With
14
8
 
15
- | Platform | Supported |
16
- |----------|-----------|
17
- | React Native CLI | ✅ |
18
- | Expo (Development Build) | ✅ |
19
- | Expo Go | ❌ (requires native code) |
9
+ - Normal screens
10
+ - React Native Modal
11
+ - Bottom sheets
12
+ - **Single-line TextInput**
13
+ - **Multiline TextInput (textarea)**
20
14
 
21
15
  ## 📦 Installation
22
16
 
@@ -26,254 +20,257 @@ npm install react-native-universal-keyboard-aware-scrollview
26
20
 
27
21
  ### iOS Setup
28
22
  ```bash
29
- cd ios && pod install
23
+ cd ios && pod install && cd ..
30
24
  ```
31
25
 
32
26
  ### Android Setup
33
- No additional setup required - auto-linking handles everything.
27
+ No additional setup needed (auto-linking).
34
28
 
35
- ### Expo (Development Build)
29
+ ### Expo
36
30
  ```bash
37
31
  npx expo prebuild
38
32
  npx expo run:ios # or run:android
39
33
  ```
40
34
 
41
- > **Note:** This package uses native modules, so it won't work in Expo Go. Use a development build instead.
35
+ > ⚠️ **Note:** This package has native code and won't work in Expo Go. Use a development build.
42
36
 
43
- ## 🚀 Quick Start
37
+ ---
44
38
 
45
- ### Basic Usage
39
+ ## 🚀 Usage
40
+
41
+ ### Basic Example
46
42
 
47
43
  ```tsx
48
44
  import React from 'react';
49
- import { TextInput, StyleSheet, SafeAreaView } from 'react-native';
45
+ import { TextInput, StyleSheet, SafeAreaView, Text } from 'react-native';
50
46
  import { KeyboardAwareScrollView } from 'react-native-universal-keyboard-aware-scrollview';
51
47
 
52
48
  export default function App() {
53
49
  return (
54
50
  <SafeAreaView style={styles.container}>
55
- <KeyboardAwareScrollView style={styles.scroll}>
51
+ <KeyboardAwareScrollView
52
+ style={styles.scroll}
53
+ extraScrollHeight={75}
54
+ >
55
+ <Text style={styles.title}>Contact Form</Text>
56
+
56
57
  <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}
58
+ <TextInput placeholder="Email" style={styles.input} keyboardType="email-address" />
59
+ <TextInput placeholder="Phone" style={styles.input} keyboardType="phone-pad" />
60
+
61
+ {/* Multiline TextInput - automatically handled! */}
62
+ <TextInput
63
+ placeholder="Your message..."
64
+ style={[styles.input, styles.textArea]}
65
+ multiline
66
+ numberOfLines={6}
67
+ textAlignVertical="top"
64
68
  />
69
+
70
+ <TextInput placeholder="Website (optional)" style={styles.input} />
65
71
  </KeyboardAwareScrollView>
66
72
  </SafeAreaView>
67
73
  );
68
74
  }
69
75
 
70
76
  const styles = StyleSheet.create({
71
- container: { flex: 1 },
72
- scroll: { flex: 1, padding: 16 },
77
+ container: {
78
+ flex: 1,
79
+ backgroundColor: '#f5f5f5'
80
+ },
81
+ scroll: {
82
+ flex: 1,
83
+ padding: 20
84
+ },
85
+ title: {
86
+ fontSize: 24,
87
+ fontWeight: 'bold',
88
+ marginBottom: 20
89
+ },
73
90
  input: {
91
+ backgroundColor: 'white',
74
92
  borderWidth: 1,
75
- borderColor: '#ccc',
93
+ borderColor: '#ddd',
76
94
  borderRadius: 8,
77
- padding: 12,
78
- marginBottom: 16,
95
+ padding: 15,
96
+ marginBottom: 15,
97
+ fontSize: 16,
98
+ },
99
+ textArea: {
100
+ height: 150,
101
+ textAlignVertical: 'top',
79
102
  },
80
- multiline: { height: 120, textAlignVertical: 'top' },
81
103
  });
82
104
  ```
83
105
 
84
- ### Usage Inside Modal (The Key Feature!)
106
+ ### Inside Modal
85
107
 
86
108
  ```tsx
87
109
  import React, { useState } from 'react';
88
- import { Modal, TextInput, View, Button, StyleSheet, SafeAreaView } from 'react-native';
110
+ import { Modal, TextInput, Button, SafeAreaView, View, Text, StyleSheet } from 'react-native';
89
111
  import { KeyboardAwareScrollView } from 'react-native-universal-keyboard-aware-scrollview';
90
112
 
91
113
  export default function ModalExample() {
92
114
  const [visible, setVisible] = useState(false);
93
115
 
94
116
  return (
95
- <>
96
- <Button title="Open Modal" onPress={() => setVisible(true)} />
97
-
117
+ <View style={styles.container}>
118
+ <Button title="Open Modal Form" onPress={() => setVisible(true)} />
119
+
98
120
  <Modal visible={visible} animationType="slide">
99
- <SafeAreaView style={styles.container}>
121
+ <SafeAreaView style={styles.modal}>
122
+ <Text style={styles.title}>Modal Form</Text>
123
+
100
124
  <KeyboardAwareScrollView
125
+ style={styles.scroll}
101
126
  insideModal={true}
102
- extraScrollHeight={20}
127
+ extraScrollHeight={80}
103
128
  >
104
- <TextInput placeholder="Name" style={styles.input} />
129
+ <TextInput placeholder="Username" style={styles.input} />
105
130
  <TextInput placeholder="Email" style={styles.input} />
106
- <TextInput
107
- placeholder="Message"
108
- style={[styles.input, styles.multiline]}
131
+ <TextInput placeholder="Password" style={styles.input} secureTextEntry />
132
+
133
+ <TextInput
134
+ placeholder="Bio (multiline)"
135
+ style={[styles.input, { height: 120 }]}
109
136
  multiline
110
- numberOfLines={4}
137
+ textAlignVertical="top"
111
138
  />
112
139
  </KeyboardAwareScrollView>
140
+
113
141
  <Button title="Close" onPress={() => setVisible(false)} />
114
142
  </SafeAreaView>
115
143
  </Modal>
116
- </>
144
+ </View>
117
145
  );
118
146
  }
119
147
 
120
148
  const styles = StyleSheet.create({
121
- container: { flex: 1 },
149
+ container: { flex: 1, justifyContent: 'center', padding: 20 },
150
+ modal: { flex: 1 },
151
+ title: { fontSize: 20, fontWeight: 'bold', padding: 20 },
152
+ scroll: { flex: 1, padding: 20 },
122
153
  input: {
154
+ backgroundColor: 'white',
123
155
  borderWidth: 1,
124
156
  borderColor: '#ccc',
125
157
  borderRadius: 8,
126
158
  padding: 12,
127
- margin: 16,
159
+ marginBottom: 12,
160
+ fontSize: 16,
128
161
  },
129
- multiline: { height: 100, textAlignVertical: 'top' },
130
162
  });
131
163
  ```
132
164
 
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
165
  ### Using the Hook
151
166
 
152
167
  ```tsx
168
+ import React from 'react';
169
+ import { View, TextInput, Button, Text, StyleSheet } from 'react-native';
153
170
  import { useKeyboard } from 'react-native-universal-keyboard-aware-scrollview';
154
171
 
155
- function MyComponent() {
156
- const {
157
- keyboardHeight,
158
- isKeyboardVisible,
159
- dismissKeyboard
160
- } = useKeyboard();
172
+ export default function HookExample() {
173
+ const { keyboardHeight, isKeyboardVisible, dismissKeyboard } = useKeyboard();
161
174
 
162
175
  return (
163
- <View style={{ paddingBottom: keyboardHeight }}>
164
- <TextInput placeholder="Type here..." />
176
+ <View style={[styles.container, { paddingBottom: keyboardHeight }]}>
177
+ <Text>Keyboard Height: {keyboardHeight}px</Text>
178
+ <Text>Visible: {isKeyboardVisible ? 'Yes' : 'No'}</Text>
179
+
180
+ <TextInput placeholder="Type something..." style={styles.input} />
181
+
165
182
  {isKeyboardVisible && (
166
- <Button title="Dismiss" onPress={dismissKeyboard} />
183
+ <Button title="Dismiss Keyboard" onPress={dismissKeyboard} />
167
184
  )}
168
185
  </View>
169
186
  );
170
187
  }
188
+
189
+ const styles = StyleSheet.create({
190
+ container: { flex: 1, padding: 20 },
191
+ input: { borderWidth: 1, borderColor: '#ccc', padding: 12, marginVertical: 20 },
192
+ });
171
193
  ```
172
194
 
173
- ## 📖 API
195
+ ---
174
196
 
175
- ### `KeyboardAwareScrollView` Props
197
+ ## 📖 Props
176
198
 
177
199
  | Prop | Type | Default | Description |
178
200
  |------|------|---------|-------------|
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** |
185
- | `enableAnimation` | `boolean` | `true` | Animate height changes |
186
- | `keyboardShouldPersistTaps` | `string` | `'handled'` | Tap behavior |
187
- | `enableAutoScrollToFocused` | `boolean` | `true` | Auto-scroll to focused input |
188
-
189
- ### `useKeyboard` Hook
201
+ | `enableOnAndroid` | boolean | `true` | Enable on Android |
202
+ | `enableOnIOS` | boolean | `true` | Enable on iOS |
203
+ | `extraScrollHeight` | number | `75` | Extra scroll padding above keyboard |
204
+ | `enableAutoScrollToFocused` | boolean | `true` | Auto-scroll to focused input |
205
+ | `insideModal` | boolean | `false` | Set true when inside Modal |
206
+ | `keyboardShouldPersistTaps` | string | `'handled'` | Keyboard tap behavior |
207
+ | `resetScrollToCoords` | object | `null` | Reset scroll on keyboard hide |
208
+ | `onKeyboardDidShow` | function | - | Called when keyboard shows |
209
+ | `onKeyboardDidHide` | function | - | Called when keyboard hides |
190
210
 
191
- ```tsx
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);
199
- ```
211
+ ---
200
212
 
201
- #### Options
213
+ ## 🔧 Ref Methods
202
214
 
203
215
  ```tsx
204
- useKeyboard({
205
- enableOnAndroid: true,
206
- enableOnIOS: true,
207
- onKeyboardDidShow: (event) => {},
208
- onKeyboardDidHide: (event) => {},
209
- onKeyboardHeightChange: (height) => {},
210
- });
211
- ```
212
-
213
- ### `KeyboardController` Utility
216
+ import { useRef } from 'react';
217
+ import { KeyboardAwareScrollView, KeyboardAwareScrollViewRef } from 'react-native-universal-keyboard-aware-scrollview';
214
218
 
215
- ```tsx
216
- import { KeyboardController } from 'react-native-universal-keyboard-aware-scrollview';
217
-
218
- // Dismiss keyboard
219
- await KeyboardController.dismiss();
219
+ function MyComponent() {
220
+ const scrollRef = useRef<KeyboardAwareScrollViewRef>(null);
220
221
 
221
- // Get current height
222
- const height = await KeyboardController.getHeight();
222
+ return (
223
+ <KeyboardAwareScrollView ref={scrollRef}>
224
+ {/* Your inputs */}
225
+ </KeyboardAwareScrollView>
226
+ );
227
+ }
223
228
 
224
- // Check if visible
225
- const visible = await KeyboardController.isVisible();
229
+ // Available methods:
230
+ // scrollRef.current?.scrollTo({ x: 0, y: 100, animated: true });
231
+ // scrollRef.current?.scrollToEnd({ animated: true });
232
+ // scrollRef.current?.scrollToInput(inputRef);
233
+ // scrollRef.current?.getScrollView();
226
234
  ```
227
235
 
228
- ## 🎯 Why This Package?
229
-
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 | ❌ | ✅ |
236
+ ---
237
237
 
238
- ### Technical Implementation
238
+ ## Supported Platforms
239
239
 
240
- **iOS:** Uses `UIKeyboardWillChangeFrameNotification` for accurate keyboard frame detection.
241
-
242
- **Android:** Uses `ViewTreeObserver.OnGlobalLayoutListener` to measure visible window frame.
240
+ | Platform | Supported |
241
+ |----------|-----------|
242
+ | iOS | |
243
+ | Android | ✅ |
244
+ | React Native CLI | ✅ |
245
+ | Expo (Development Build) | ✅ |
246
+ | Expo Go | ❌ |
243
247
 
244
- Both approaches work correctly inside modals because they measure the actual visible area, not the root view.
248
+ ---
245
249
 
246
250
  ## 🐛 Troubleshooting
247
251
 
248
- ### "Native module not found"
249
-
252
+ ### Keyboard not detected
250
253
  ```bash
251
- # iOS
254
+ # iOS - reinstall pods
252
255
  cd ios && pod install --repo-update && cd ..
253
256
 
254
257
  # Android - clean build
255
258
  cd android && ./gradlew clean && cd ..
256
259
  ```
257
260
 
258
- ### Expo Go not working
259
-
260
- This package requires native code. Use:
261
- ```bash
262
- npx expo prebuild
263
- npx expo run:ios # or npx expo run:android
264
- ```
265
-
266
- ### Multiline TextInput not scrolling properly
267
-
261
+ ### Multiline TextInput not scrolling
268
262
  Make sure you're using the `multiline` prop:
269
263
  ```tsx
270
- <TextInput
271
- multiline // Required!
264
+ <TextInput
265
+ multiline={true} // Required!
272
266
  numberOfLines={4}
267
+ textAlignVertical="top" // For Android
273
268
  style={{ height: 100 }}
274
269
  />
275
270
  ```
276
271
 
272
+ ---
273
+
277
274
  ## 📄 License
278
275
 
279
- MIT © [Vijay Kishan](https://github.com/AetherTechDev)
276
+ MIT © Vijay Kishan
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-universal-keyboard-aware-scrollview",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
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",
@@ -1,61 +1,65 @@
1
+ /**
2
+ * Native Module wrapper
3
+ *
4
+ * This file provides access to the native keyboard module when available.
5
+ * Falls back gracefully to React Native's Keyboard API when native module is not linked.
6
+ */
7
+
1
8
  import { NativeModules, NativeEventEmitter, Platform } from 'react-native';
2
- import type { UniversalKeyboardNativeModule, KeyboardEvent } from './types';
3
9
 
4
10
  const LINKING_ERROR =
5
11
  `The package 'react-native-universal-keyboard-aware-scrollview' doesn't seem to be linked. Make sure: \n\n` +
6
12
  Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) +
7
13
  '- You rebuilt the app after installing the package\n' +
8
- '- You are not using Expo Go (use development build instead)\n';
14
+ '- You are not using Expo Go (use development build instead)\n\n' +
15
+ 'The package will use React Native Keyboard API as fallback.';
9
16
 
10
17
  /**
11
- * Native module instance
18
+ * Native module interface
12
19
  */
13
- const NativeModule: UniversalKeyboardNativeModule | undefined =
14
- NativeModules.UniversalKeyboard;
20
+ interface UniversalKeyboardNativeModule {
21
+ startListening(): Promise<boolean>;
22
+ stopListening(): Promise<boolean>;
23
+ getKeyboardHeight(): Promise<number>;
24
+ isKeyboardVisible(): Promise<boolean>;
25
+ dismissKeyboard(): Promise<boolean>;
26
+ addListener(eventName: string): void;
27
+ removeListeners(count: number): void;
28
+ }
15
29
 
16
- /**
17
- * Proxy that throws a helpful error if the native module is not linked
18
- */
19
- export const UniversalKeyboardModule: UniversalKeyboardNativeModule = NativeModule
20
- ? NativeModule
21
- : new Proxy({} as UniversalKeyboardNativeModule, {
22
- get() {
23
- throw new Error(LINKING_ERROR);
24
- },
25
- });
30
+ // Get native module
31
+ const NativeModule = NativeModules.UniversalKeyboard as UniversalKeyboardNativeModule | undefined;
26
32
 
27
33
  /**
28
- * Event emitter for keyboard events
34
+ * Check if native module is available
29
35
  */
30
- export const KeyboardEventEmitter = NativeModule
31
- ? new NativeEventEmitter(NativeModule as any)
32
- : null;
36
+ export function isNativeModuleAvailable(): boolean {
37
+ return NativeModule != null;
38
+ }
33
39
 
34
40
  /**
35
- * Subscribe to keyboard events
36
- * @param eventName - Name of the event to subscribe to
37
- * @param callback - Callback function to execute when event fires
38
- * @returns Cleanup function to unsubscribe
41
+ * Native module with fallback
39
42
  */
40
- export function subscribeToKeyboardEvent(
41
- eventName: string,
42
- callback: (event: KeyboardEvent) => void
43
- ): () => void {
44
- if (!KeyboardEventEmitter) {
43
+ export const UniversalKeyboardModule: UniversalKeyboardNativeModule = NativeModule || {
44
+ startListening: async () => {
45
45
  console.warn(LINKING_ERROR);
46
- return () => {};
47
- }
48
-
49
- const subscription = KeyboardEventEmitter.addListener(eventName, callback);
50
-
51
- return () => {
52
- subscription.remove();
53
- };
54
- }
46
+ return false;
47
+ },
48
+ stopListening: async () => false,
49
+ getKeyboardHeight: async () => 0,
50
+ isKeyboardVisible: async () => false,
51
+ dismissKeyboard: async () => {
52
+ const { Keyboard } = require('react-native');
53
+ Keyboard.dismiss();
54
+ return true;
55
+ },
56
+ addListener: () => {},
57
+ removeListeners: () => {},
58
+ };
55
59
 
56
60
  /**
57
- * Check if the native module is available
61
+ * Event emitter for native keyboard events
58
62
  */
59
- export function isNativeModuleAvailable(): boolean {
60
- return NativeModule != null;
61
- }
63
+ export const KeyboardEventEmitter = NativeModule
64
+ ? new NativeEventEmitter(NativeModule as any)
65
+ : null;