react-native-keyboard-toolkit 1.0.0 → 1.0.1

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
@@ -2,78 +2,19 @@
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/react-native-keyboard-toolkit.svg)](https://www.npmjs.com/package/react-native-keyboard-toolkit)
4
4
  [![license](https://img.shields.io/npm/l/react-native-keyboard-toolkit.svg)](LICENSE)
5
- [![platforms](https://img.shields.io/badge/platforms-iOS%20%7C%20Android-blue.svg)](#supported-platforms)
6
-
7
- Keyboard-aware scrolling, layout helpers, hooks, and focus utilities for React Native.
8
-
9
- `react-native-keyboard-toolkit` is a **JavaScript-only** library. It is built on React Native's own `Keyboard` events and view measurements (`measureInWindow`), so it has **no native module, no CocoaPods step, no manual linking, and no Babel plugin**.
10
-
11
- - [Features](#features)
12
- - [Supported platforms](#supported-platforms)
13
- - [Compatibility and requirements](#compatibility-and-requirements)
14
- - [Installation](#installation)
15
- - [Setup](#setup)
16
- - [Basic usage](#basic-usage)
17
- - [Components](#components)
18
- - [`KeyboardAwareScrollView`](#keyboardawarescrollview)
19
- - [`KeyboardAwareView`](#keyboardawareview)
20
- - [`KeyboardToolbar`](#keyboardtoolbar)
21
- - [`KeyboardDismissButton`](#keyboarddismissbutton)
22
- - [`KeyboardProvider`](#keyboardprovider)
23
- - [Hooks](#hooks)
24
- - [`useKeyboardState` / `useKeyboard`](#usekeyboardstate--usekeyboard)
25
- - [`useKeyboardAnimation`](#usekeyboardanimation)
26
- - [`useFocusedInput`](#usefocusedinput)
27
- - [Utility functions](#utility-functions)
28
- - [Complete React Native CLI example](#complete-react-native-cli-example)
29
- - [TypeScript](#typescript)
30
- - [API reference](#api-reference)
31
- - [How it works](#how-it-works)
32
- - [Troubleshooting](#troubleshooting)
33
- - [Example project](#example-project)
34
- - [Contributing](#contributing)
35
- - [License](#license)
36
- - [Links](#links)
5
+ ![platforms](https://img.shields.io/badge/platforms-iOS%20%7C%20Android-blue.svg)
37
6
 
38
- ## Features
39
-
40
- - **`KeyboardAwareScrollView`** – a `ScrollView` that adds space for the keyboard and scrolls a covered, focused `TextInput` into view.
41
- - **`KeyboardAwareView`** – a lightweight `View` that adds bottom space while the keyboard covers it.
42
- - **`KeyboardToolbar`** – an in-layout toolbar with Previous / Next / Done controls for moving between input refs.
43
- - **`KeyboardDismissButton`** – an accessible pressable that dismisses the keyboard.
44
- - **Hooks** – `useKeyboardState` (alias `useKeyboard`), `useKeyboardAnimation`, and `useFocusedInput`.
45
- - **Shared keyboard state** – one native event subscription is shared by every component and hook, and it is cleaned up automatically.
46
- - **Focus and layout utilities** – `getFocusedInput`, `measureFocusedInput`, `focusInput`, `calculateKeyboardOverlap`, and `calculateScrollOffset`.
47
- - **Overlap-based adjustment** – components measure how much of _themselves_ the keyboard actually covers instead of blindly adding the full keyboard height, which avoids double spacing when Android has already resized the window.
48
- - **Five layout behaviors** – `smart`, `scroll`, `padding`, `resize`, and `none`.
49
- - **Written in TypeScript** – type declarations are bundled.
50
-
51
- ## Supported platforms
52
-
53
- | Platform | Support |
54
- | ------------------- | ----------------------------------------------------------------------- |
55
- | iOS | Supported |
56
- | Android | Supported |
57
- | Expo | JavaScript-only, so no config plugin is needed (not tested – see below) |
58
- | Web, Windows, macOS | Not supported |
59
-
60
- ## Compatibility and requirements
7
+ Keyboard-aware scroll view, sticky submit button, input toolbar, and keyboard hooks for React Native on iOS and Android. Keep focused `TextInput` fields visible, move footers above the keyboard, and read keyboard state — all in plain TypeScript, with no native module, linking, or config plugin.
61
8
 
62
- | Requirement | Version |
63
- | -------------- | --------------------------------------------------------------------------- |
64
- | `react` | `>=18.2.0` (uses `useSyncExternalStore`) |
65
- | `react-native` | `>=0.71.0` |
66
- | TypeScript | Optional. Declarations are included and work with `@types/react` 18 and 19. |
67
-
68
- The package was checked against these combinations (TypeScript compile of an app that imports the packed package, Jest render of every component and hook, and a Metro bundle where noted):
69
-
70
- | React Native | React | Type check | Jest render | Metro bundle |
71
- | ------------ | ------ | :--------: | :---------: | :---------------: |
72
- | 0.71.19 | 18.2.0 | ✅ | ✅ | ✅ |
73
- | 0.76.9 | 18.3.1 | ✅ | ✅ | – |
74
- | 0.88.1 | 19.3.0 | ✅ | ✅ | ✅ (Android, iOS) |
9
+ ## Features
75
10
 
76
- These are automated checks. **The library has not been validated on physical devices or simulators/emulators**, and Expo SDK projects have not been tested. Keyboard geometry differs between devices, OS versions, keyboards, and window configurations, so please test the screens you ship. See [`docs/ios.md`](docs/ios.md) and [`docs/android.md`](docs/android.md) for device checklists.
11
+ - **`KeyboardAwareScrollView`** – a `ScrollView` that scrolls a covered input into view.
12
+ - **`KeyboardAwareView`** – adds bottom space to normal content, or lifts an absolutely positioned footer above the keyboard (sticky button).
13
+ - **`KeyboardToolbar`** – Previous / Next / Done controls for moving between inputs.
14
+ - **`KeyboardDismissButton`** – a `Pressable` that dismisses the keyboard.
15
+ - **Hooks** – `useKeyboardState` (alias `useKeyboard`), `useKeyboardAnimation`, `useFocusedInput`.
16
+ - **Utilities** – `focusInput`, `getFocusedInput`, `measureFocusedInput`, `calculateKeyboardOverlap`, `calculateScrollOffset`, `isIOS`, `isAndroid`.
17
+ - Written in TypeScript with bundled type declarations.
77
18
 
78
19
  ## Installation
79
20
 
@@ -81,935 +22,236 @@ These are automated checks. **The library has not been validated on physical dev
81
22
  npm install react-native-keyboard-toolkit
82
23
  # or
83
24
  yarn add react-native-keyboard-toolkit
84
- # or
85
- pnpm add react-native-keyboard-toolkit
86
- ```
87
-
88
- ### Required peer dependencies
89
-
90
- These must already be in your app (every React Native app has them):
91
-
92
- | Package | Version | Why |
93
- | -------------- | ---------- | ------------------------------------------------------- |
94
- | `react` | `>=18.2.0` | Hooks and `useSyncExternalStore`. |
95
- | `react-native` | `>=0.71.0` | `Keyboard`, `TextInput`, `ScrollView`, `Animated`, etc. |
96
-
97
- ### Optional dependencies
98
-
99
- The library has **no other runtime dependencies** and does not import any third-party package.
100
-
101
- | Package | When you might want it |
102
- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
103
- | `react-native-safe-area-context` | To lay out your screens inside the safe area (notch, home indicator). **Not required by this library** – it is only used by the repository's demo app. |
104
-
105
- If you want it, install it as described in its own documentation:
106
-
107
- ```sh
108
- npm install react-native-safe-area-context
109
25
  ```
110
26
 
111
- ### Dependency installation checklist
27
+ **Requirements:** React `>=18.2.0`, React Native `>=0.71.0`. No iOS setup is needed.
112
28
 
113
- 1. `npm install react-native-keyboard-toolkit`
114
- 2. Nothing else for the library itself. There is no `pod install` step _for this package_. (As usual in a React Native CLI app, run `cd ios && pod install` after adding any other native dependency.)
115
- 3. Restart Metro (`npx react-native start --reset-cache`) if it was already running.
116
-
117
- ## Setup
118
-
119
- ### React Native CLI
120
-
121
- No native changes are required for the library. For the best results on Android, keep the standard resizing window mode on your activity (the React Native CLI template already uses it):
29
+ On Android, set `adjustResize` on your activity in `AndroidManifest.xml`:
122
30
 
123
31
  ```xml
124
- <!-- android/app/src/main/AndroidManifest.xml -->
125
32
  <activity
126
33
  android:name=".MainActivity"
127
34
  android:windowSoftInputMode="adjustResize"
128
35
  ... />
129
36
  ```
130
37
 
131
- Keep the other attributes of your existing `MainActivity` declaration. If you use `adjustPan` or an edge-to-edge configuration, see [Troubleshooting](#troubleshooting) and [`docs/android.md`](docs/android.md).
132
-
133
- On iOS there is nothing to configure.
134
-
135
- ### Expo
38
+ ## Quick start
136
39
 
137
- The package contains only JavaScript, so it needs no config plugin or prebuild step. This has not been tested in an Expo project by the maintainers; please report any problems.
138
-
139
- ### Provider (optional)
140
-
141
- Wrapping your app in [`KeyboardProvider`](#keyboardprovider) is **optional**. The components and hooks work without it. It only enables development logging through its `debug` prop.
142
-
143
- ## Basic usage
40
+ ### Keyboard-aware form
144
41
 
145
42
  ```tsx
146
43
  import React from 'react';
147
44
  import { TextInput } from 'react-native';
148
- import {
149
- KeyboardAwareScrollView,
150
- KeyboardProvider,
151
- } from 'react-native-keyboard-toolkit';
152
-
153
- export default function ProfileForm(): React.JSX.Element {
154
- return (
155
- <KeyboardProvider>
156
- <KeyboardAwareScrollView
157
- behavior="smart"
158
- keyboardShouldPersistTaps="handled"
159
- keyboardSpacing={16}
160
- contentContainerStyle={{ padding: 20, gap: 12 }}
161
- >
162
- <TextInput placeholder="Name" />
163
- <TextInput placeholder="Email" keyboardType="email-address" />
164
- <TextInput placeholder="Notes" multiline />
165
- </KeyboardAwareScrollView>
166
- </KeyboardProvider>
167
- );
168
- }
169
- ```
170
-
171
- All public exports come from the package root:
172
-
173
- ```ts
174
- import {
175
- // components
176
- KeyboardAwareScrollView,
177
- KeyboardAwareView,
178
- KeyboardToolbar,
179
- KeyboardDismissButton,
180
- KeyboardProvider,
181
- // hooks
182
- useKeyboard,
183
- useKeyboardState,
184
- useKeyboardAnimation,
185
- useFocusedInput,
186
- // utilities
187
- calculateKeyboardOverlap,
188
- calculateScrollOffset,
189
- focusInput,
190
- getFocusedInput,
191
- measureFocusedInput,
192
- isAndroid,
193
- isIOS,
194
- } from 'react-native-keyboard-toolkit';
195
- ```
196
-
197
- Deep imports (for example `react-native-keyboard-toolkit/src/...`) are not supported.
198
-
199
- ## Components
200
-
201
- ### `KeyboardAwareScrollView`
202
-
203
- A drop-in replacement for React Native's `ScrollView`. It accepts every `ScrollViewProps` plus the props below, and forwards a `ref` to the underlying `ScrollView` instance.
204
-
205
- | Prop | Type | Default | Description |
206
- | ----------------- | -------------------------------------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------- |
207
- | `behavior` | `'smart' \| 'scroll' \| 'padding' \| 'resize' \| 'none'` | `'smart'` | Layout strategy. See [Behaviors](#behaviors). |
208
- | `keyboardSpacing` | `number` | `16` | Space, in points, kept between the focused input / content and the keyboard. Negative values are treated as `0`. |
209
- | `enabled` | `boolean` | `true` | When `false`, keyboard padding and focus scrolling are disabled and it behaves like a normal `ScrollView`. |
210
-
211
- `scrollEventThrottle` defaults to `16`. The component wraps `onScroll`, `onScrollBeginDrag`, `onContentSizeChange`, `onLayout`, and `onFocusCapture`; your handlers are still called.
212
-
213
- ```tsx
214
- import React from 'react';
215
- import { Pressable, Text, TextInput } from 'react-native';
216
45
  import { KeyboardAwareScrollView } from 'react-native-keyboard-toolkit';
217
46
 
218
- export function ContactForm(): React.JSX.Element {
47
+ export function ContactForm() {
219
48
  return (
220
49
  <KeyboardAwareScrollView
221
- behavior="smart"
222
- keyboardSpacing={20}
223
50
  keyboardShouldPersistTaps="handled"
224
- keyboardDismissMode="interactive"
225
- contentContainerStyle={{ padding: 20, paddingBottom: 28 }}
51
+ keyboardSpacing={16}
52
+ contentContainerStyle={{ padding: 20 }}
226
53
  >
227
54
  <TextInput placeholder="Name" />
228
55
  <TextInput placeholder="Email" keyboardType="email-address" />
229
56
  <TextInput placeholder="Message" multiline />
230
- <Pressable onPress={() => {}}>
231
- <Text>Submit</Text>
232
- </Pressable>
233
- </KeyboardAwareScrollView>
234
- );
235
- }
236
- ```
237
-
238
- **Using the ref** (for example to call `scrollTo`):
239
-
240
- ```tsx
241
- import React, { useRef } from 'react';
242
- import { Pressable, Text, TextInput } from 'react-native';
243
- import {
244
- KeyboardAwareScrollView,
245
- type KeyboardAwareScrollViewRef,
246
- } from 'react-native-keyboard-toolkit';
247
-
248
- export function ScrollToTopForm(): React.JSX.Element {
249
- const scrollRef = useRef<KeyboardAwareScrollViewRef>(null);
250
-
251
- return (
252
- <KeyboardAwareScrollView ref={scrollRef}>
253
- <TextInput placeholder="Name" />
254
- <TextInput placeholder="Email" />
255
- <Pressable
256
- onPress={() => scrollRef.current?.scrollTo({ y: 0, animated: true })}
257
- >
258
- <Text>Back to top</Text>
259
- </Pressable>
260
57
  </KeyboardAwareScrollView>
261
58
  );
262
59
  }
263
60
  ```
264
61
 
265
- #### Behaviors
266
-
267
- | `behavior` | Adds bottom padding for the keyboard | Scrolls the focused input into view | Notes |
268
- | ----------- | :----------------------------------: | :---------------------------------: | --------------------------------------------------------------------------------------------------------------------------------------------------- |
269
- | `'smart'` | ✅ | ✅ (only if it is covered) | Default. Currently identical to `'scroll'`. |
270
- | `'scroll'` | ✅ | ✅ (only if it is covered) | Currently identical to `'smart'`. |
271
- | `'padding'` | ✅ | ❌ | Extends the scroll range only; you decide what scrolls. |
272
- | `'resize'` | ❌ | ❌ | On iOS, turns on `ScrollView`'s `automaticallyAdjustKeyboardInsets` (unless you set that prop yourself). On Android, relies on the window resizing. |
273
- | `'none'` | ❌ | ❌ | No keyboard adjustment. |
274
-
275
- Details that are useful to know:
276
-
277
- - The padding added is `max(existing paddingBottom, keyboardOverlap + keyboardSpacing)`. An existing numeric `paddingBottom` is preserved, not added to. A non-numeric (string) `paddingBottom` disables the extra padding.
278
- - Padding is based on the part of the scroll view's viewport the keyboard actually covers. If Android's `adjustResize` has already shrunk the view, the overlap is `0` and no extra padding is added.
279
- - Do not stack multiple keyboard-padding wrappers (for example your own `KeyboardAvoidingView`) around the same content unless you want extra spacing.
280
-
281
- ### `KeyboardAwareView`
282
-
283
- A `View` that adds bottom padding for the part of itself that the keyboard covers. It accepts every `ViewProps` plus the same three props as the scroll view, and forwards a `ref` to the underlying `View`.
284
-
285
- | Prop | Type | Default | Description |
286
- | ----------------- | -------------------------------------------------------- | --------- | ------------------------------------------------------------------------- |
287
- | `behavior` | `'smart' \| 'scroll' \| 'padding' \| 'resize' \| 'none'` | `'smart'` | `smart`, `scroll`, and `padding` add padding; `resize` and `none` do not. |
288
- | `keyboardSpacing` | `number` | `16` | Extra space, in points, between the content and the keyboard. |
289
- | `enabled` | `boolean` | `true` | When `false`, the view never adds padding. |
62
+ ### Sticky submit button
290
63
 
291
- It does **not** scroll anything. Use [`KeyboardAwareScrollView`](#keyboardawarescrollview) for long or scrollable forms. As with the scroll view, a numeric `paddingBottom` in `style` is preserved (the larger value wins) and a string `paddingBottom` disables the adjustment.
64
+ Put a `KeyboardAwareView` with `position: 'absolute'` next to the scroll view. It animates above the keyboard and returns to its original place when the keyboard closes.
292
65
 
293
66
  ```tsx
294
67
  import React from 'react';
295
- import { Pressable, Text, TextInput } from 'react-native';
296
- import { KeyboardAwareView } from 'react-native-keyboard-toolkit';
297
-
298
- export function ShortForm(): React.JSX.Element {
299
- return (
300
- <KeyboardAwareView
301
- behavior="smart"
302
- keyboardSpacing={12}
303
- style={{ flex: 1, justifyContent: 'space-between', padding: 20 }}
304
- >
305
- <TextInput placeholder="One short field" />
306
- <Pressable onPress={() => {}}>
307
- <Text>Continue</Text>
308
- </Pressable>
309
- </KeyboardAwareView>
310
- );
311
- }
312
- ```
313
-
314
- ### `KeyboardToolbar`
315
-
316
- An **in-layout** toolbar (a regular `View`, not a native iOS input accessory view) with _Previous_, _Next_, and _Done_ controls. Pass the refs of your inputs in navigation order.
317
-
318
- | Prop | Type | Default | Description |
319
- | -------------------------- | -------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------ |
320
- | `inputRefs` | `readonly RefObject<TextInputRef \| null>[]` | `[]` | Refs of the inputs to navigate between, in order. Null / unmounted refs are skipped. |
321
- | `onFocusedInputChange` | `(index: number) => void` | – | Called when the focused input among `inputRefs` changes. `index` is `-1` when none of them is focused. |
322
- | `onDone` | `() => void` | – | Called after _Done_ has blurred the input and dismissed the keyboard. |
323
- | `doneText` | `string` | `'Done'` | Label of the built-in _Done_ button. |
324
- | `alwaysVisible` | `boolean` | `false` | By default the toolbar renders `null` while the keyboard is hidden. Set `true` to keep it rendered. |
325
- | `children` | `ReactNode` | – | Replaces the built-in Previous / Next / Done controls entirely. |
326
- | `style`, other `ViewProps` | `ViewProps` | – | Applied to the toolbar container. |
327
- | `focusedInputIndex` | `number` | – | **Deprecated.** Focus is detected automatically from `inputRefs`. |
328
-
329
- Behavior notes:
330
-
331
- - The focused input is detected by calling `isFocused()` on the supplied refs (polled every 100 ms while the toolbar is active), so you do not need to track an index yourself.
332
- - _Previous_ / _Next_ call `focus()` on the nearest mounted ref in that direction and are disabled when there is none.
333
- - _Done_ blurs the currently focused native input, calls `Keyboard.dismiss()`, then calls `onDone`.
334
- - The built-in button labels ("Previous", "Next") and accessibility labels are English only. Pass `children` to render your own controls.
335
- - Because it is a normal view, place it in your layout above the keyboard. The pattern used by the demo app is to put it at the bottom of the screen, wrapped in a `KeyboardAwareView` so it is lifted above the keyboard.
336
-
337
- ```tsx
338
- import React, { useRef } from 'react';
339
- import { StyleSheet, TextInput, View } from 'react-native';
68
+ import { Pressable, Text, TextInput, View } from 'react-native';
340
69
  import {
341
70
  KeyboardAwareScrollView,
342
71
  KeyboardAwareView,
343
- KeyboardToolbar,
344
- type TextInputRef,
345
72
  } from 'react-native-keyboard-toolkit';
346
73
 
347
- export function LoginScreen(): React.JSX.Element {
348
- const emailRef = useRef<TextInputRef>(null);
349
- const passwordRef = useRef<TextInputRef>(null);
350
-
74
+ export function StickyForm() {
351
75
  return (
352
- <View style={styles.screen}>
76
+ <View style={{ flex: 1 }}>
353
77
  <KeyboardAwareScrollView
354
78
  keyboardShouldPersistTaps="handled"
355
- contentContainerStyle={styles.content}
79
+ keyboardSpacing={112} // leave room for the footer
80
+ contentContainerStyle={{ padding: 20, paddingBottom: 96 }}
356
81
  >
357
- <TextInput
358
- ref={emailRef}
359
- placeholder="Email"
360
- keyboardType="email-address"
361
- autoCapitalize="none"
362
- returnKeyType="next"
363
- onSubmitEditing={() => passwordRef.current?.focus()}
364
- />
365
- <TextInput
366
- ref={passwordRef}
367
- placeholder="Password"
368
- secureTextEntry
369
- returnKeyType="done"
370
- />
82
+ <TextInput placeholder="Name" />
83
+ <TextInput placeholder="Email" keyboardType="email-address" />
84
+ <TextInput placeholder="Company" />
85
+ <TextInput placeholder="Message" multiline />
371
86
  </KeyboardAwareScrollView>
372
87
 
373
- <KeyboardAwareView keyboardSpacing={0}>
374
- <KeyboardToolbar
375
- inputRefs={[emailRef, passwordRef]}
376
- doneText="Hide keyboard"
377
- onFocusedInputChange={index => {
378
- // index is -1 when none of the supplied refs is focused.
379
- console.log('Focused input index:', index);
380
- }}
381
- onDone={() => console.log('Keyboard dismissed')}
382
- />
88
+ <KeyboardAwareView
89
+ keyboardSpacing={12}
90
+ style={{
91
+ position: 'absolute',
92
+ left: 0,
93
+ right: 0,
94
+ bottom: 0,
95
+ padding: 16,
96
+ backgroundColor: 'white',
97
+ }}
98
+ >
99
+ <Pressable onPress={() => console.log('Submit')}>
100
+ <Text>Submit</Text>
101
+ </Pressable>
383
102
  </KeyboardAwareView>
384
103
  </View>
385
104
  );
386
105
  }
387
-
388
- const styles = StyleSheet.create({
389
- screen: { flex: 1 },
390
- content: { padding: 20, gap: 12 },
391
- });
392
106
  ```
393
107
 
394
- Custom toolbar content:
108
+ Set the scroll view's `keyboardSpacing` to roughly your footer height so focused inputs are not hidden behind it.
109
+
110
+ ### Previous / Next / Done toolbar
111
+
112
+ Pass your input refs in navigation order. The toolbar renders only while the keyboard is visible.
395
113
 
396
114
  ```tsx
397
- import React from 'react';
398
- import { Text } from 'react-native';
115
+ import React, { useRef } from 'react';
116
+ import { TextInput, View } from 'react-native';
399
117
  import {
400
- KeyboardDismissButton,
118
+ KeyboardAwareScrollView,
401
119
  KeyboardToolbar,
120
+ type TextInputRef,
402
121
  } from 'react-native-keyboard-toolkit';
403
122
 
404
- export function CustomToolbar(): React.JSX.Element {
405
- return (
406
- <KeyboardToolbar style={{ justifyContent: 'flex-end' }}>
407
- <KeyboardDismissButton>
408
- <Text>Close keyboard</Text>
409
- </KeyboardDismissButton>
410
- </KeyboardToolbar>
411
- );
412
- }
413
- ```
414
-
415
- ### `KeyboardDismissButton`
416
-
417
- An accessible `Pressable` (`accessibilityRole="button"`) that dismisses the software keyboard.
418
-
419
- | Prop | Type | Default | Description |
420
- | -------------------- | ---------------------- | -------------------- | --------------------------------------------------------------------------------------- |
421
- | `children` | `ReactNode` | – (required) | Button content. |
422
- | `onPress` | `() => void` | – | Called first; the keyboard is dismissed afterwards unless `dismissKeyboard` is `false`. |
423
- | `dismissKeyboard` | `boolean` | `true` | Set `false` to run `onPress` without dismissing the keyboard. |
424
- | `accessibilityLabel` | `string` | `'Dismiss keyboard'` | Accessibility label. |
425
- | `style` | `StyleProp<ViewStyle>` | – | Style of the pressable (function styles are not supported). |
426
-
427
- It also accepts the other `ViewProps`, which are passed through to the underlying `Pressable`.
123
+ export function SignUpForm() {
124
+ const name = useRef<TextInputRef>(null);
125
+ const email = useRef<TextInputRef>(null);
126
+ const password = useRef<TextInputRef>(null);
428
127
 
429
- ```tsx
430
- import React from 'react';
431
- import { Text } from 'react-native';
432
- import { KeyboardDismissButton } from 'react-native-keyboard-toolkit';
433
-
434
- export function DismissControl(): React.JSX.Element {
435
128
  return (
436
- <KeyboardDismissButton
437
- accessibilityLabel="Close keyboard"
438
- onPress={() => console.log('Dismiss pressed')}
439
- style={{ padding: 12 }}
440
- >
441
- <Text>Dismiss keyboard</Text>
442
- </KeyboardDismissButton>
443
- );
444
- }
445
- ```
446
-
447
- ### `KeyboardProvider`
448
-
449
- Optional. It renders its children unchanged and, when `debug` is `true`, logs keyboard visibility/height changes with `console.debug('[keyboard-toolkit]', state)` and the scroll calculations of `KeyboardAwareScrollView`. Logging only happens in development builds (`__DEV__`).
450
-
451
- | Prop | Type | Default | Description |
452
- | ---------- | ----------- | ------- | ---------------------------------- |
453
- | `children` | `ReactNode` | – | Your app or screen. |
454
- | `debug` | `boolean` | `false` | Log keyboard state in development. |
455
-
456
- ```tsx
457
- import React from 'react';
458
- import { KeyboardProvider } from 'react-native-keyboard-toolkit';
459
- import { Screens } from './Screens';
460
-
461
- export default function App(): React.JSX.Element {
462
- return (
463
- <KeyboardProvider debug={__DEV__}>
464
- <Screens />
465
- </KeyboardProvider>
466
- );
467
- }
468
- ```
469
-
470
- ## Hooks
471
-
472
- All hooks share a single native subscription and work with or without `KeyboardProvider`.
473
-
474
- ### `useKeyboardState` / `useKeyboard`
475
-
476
- `useKeyboard()` is an alias for `useKeyboardState()`. Both return the current `KeyboardState`:
477
-
478
- ```ts
479
- interface KeyboardState {
480
- isVisible: boolean;
481
- height: number; // keyboard frame height in points; 0 when hidden
482
- progress: 0 | 1; // 1 when visible, 0 when hidden
483
- animationDuration: number; // native event duration in ms
484
- screenY?: number; // top edge of the keyboard on screen, when reported
485
- }
486
- ```
487
-
488
- `height` and `screenY` describe the **keyboard frame**, not the overlap with a particular view; the aware components measure their own overlap. On Android, React Native reports a `duration` of `0`, so `animationDuration` is `0` there.
489
-
490
- ```tsx
491
- import React from 'react';
492
- import { Text, View } from 'react-native';
493
- import { useKeyboardState } from 'react-native-keyboard-toolkit';
494
-
495
- export function KeyboardIndicator(): React.JSX.Element {
496
- const { isVisible, height, screenY } = useKeyboardState();
129
+ <View style={{ flex: 1 }}>
130
+ <KeyboardAwareScrollView keyboardShouldPersistTaps="handled">
131
+ <TextInput ref={name} placeholder="Name" />
132
+ <TextInput ref={email} placeholder="Email" />
133
+ <TextInput ref={password} placeholder="Password" secureTextEntry />
134
+ </KeyboardAwareScrollView>
497
135
 
498
- return (
499
- <View>
500
- <Text>{isVisible ? `Keyboard: ${height} pt` : 'Keyboard hidden'}</Text>
501
- {screenY !== undefined && <Text>Top edge: {screenY} pt</Text>}
136
+ <KeyboardToolbar inputRefs={[name, email, password]} />
502
137
  </View>
503
138
  );
504
139
  }
505
140
  ```
506
141
 
507
- #### Reacting to keyboard events
508
-
509
- The hooks re-render your component whenever the keyboard state changes, so any effect that depends on the state is a keyboard event handler:
142
+ ## API
510
143
 
511
- ```tsx
512
- import React, { useEffect } from 'react';
513
- import { Text } from 'react-native';
514
- import { useKeyboard } from 'react-native-keyboard-toolkit';
515
-
516
- export function KeyboardLogger(): React.JSX.Element {
517
- const keyboard = useKeyboard();
518
-
519
- useEffect(() => {
520
- if (keyboard.isVisible) {
521
- console.log(`Keyboard opened (${keyboard.height} pt)`);
522
- } else {
523
- console.log('Keyboard closed');
524
- }
525
- }, [keyboard.isVisible, keyboard.height]);
526
-
527
- return <Text>{keyboard.isVisible ? 'Typing…' : 'Idle'}</Text>;
528
- }
529
- ```
144
+ ### `KeyboardAwareScrollView`
530
145
 
531
- ### `useKeyboardAnimation`
146
+ Accepts all `ScrollView` props, forwards its ref to the `ScrollView`, and adds:
532
147
 
533
- Returns `{ progress: Animated.Value; animationDuration: number }`. `progress` animates between `0` (hidden) and `1` (visible) over the native event duration, and jumps instantly when the system _Reduce Motion_ setting is enabled.
148
+ | Prop | Type | Default | Description |
149
+ | ----------------- | -------------------------------------------------------- | --------- | ------------------------------------------------------------------------------------ |
150
+ | `behavior` | `'smart' \| 'scroll' \| 'resize' \| 'padding' \| 'none'` | `'smart'` | See [Behaviors](#behaviors). |
151
+ | `keyboardSpacing` | `number` | `16` | Gap (points) between the focused input and the keyboard. Negative values become `0`. |
152
+ | `enabled` | `boolean` | `true` | Turns keyboard padding and focus scrolling on or off. |
534
153
 
535
- The animation uses the native driver, so apply `progress` only to properties the native driver supports (such as `opacity` and `transform`), not to layout properties like `height`.
154
+ ### `KeyboardAwareView`
536
155
 
537
- ```tsx
538
- import React from 'react';
539
- import { Animated, Text } from 'react-native';
540
- import { useKeyboardAnimation } from 'react-native-keyboard-toolkit';
156
+ Accepts all `View` props, forwards its ref to the native view, and supports `behavior`, `keyboardSpacing` (default `16`), and `enabled`.
541
157
 
542
- export function AnimatedKeyboardLabel(): React.JSX.Element {
543
- const { progress } = useKeyboardAnimation();
158
+ - **Normal layout:** adds bottom padding equal to the part of the view covered by the keyboard.
159
+ - **`position: 'absolute'`:** animates the view up so it clears the keyboard plus `keyboardSpacing`.
160
+ - It does not scroll to reveal hidden children.
544
161
 
545
- return (
546
- <Animated.View
547
- style={{
548
- opacity: progress,
549
- transform: [
550
- {
551
- translateY: progress.interpolate({
552
- inputRange: [0, 1],
553
- outputRange: [12, 0],
554
- }),
555
- },
556
- ],
557
- }}
558
- >
559
- <Text>Keyboard is visible</Text>
560
- </Animated.View>
561
- );
562
- }
563
- ```
162
+ ### `KeyboardToolbar`
564
163
 
565
- ### `useFocusedInput`
164
+ Accepts all `View` props plus:
566
165
 
567
- Returns `{ focusedInput, refresh }`.
166
+ | Prop | Type | Default | Description |
167
+ | ---------------------- | ----------------------------------- | -------- | ---------------------------------------------------------------------------------------------------- |
168
+ | `inputRefs` | `RefObject<TextInputRef \| null>[]` | `[]` | Inputs in navigation order. The focused input is detected automatically; unmounted refs are skipped. |
169
+ | `onFocusedInputChange` | `(index: number) => void` | – | Called when the focused input changes (`-1` = none of `inputRefs`). |
170
+ | `onDone` | `() => void` | – | Called after Done dismisses the keyboard. |
171
+ | `doneText` | `string` | `'Done'` | Label of the Done button. |
172
+ | `alwaysVisible` | `boolean` | `false` | Show the toolbar even when the keyboard is hidden. |
173
+ | `children` | `ReactNode` | – | Replaces the built-in Previous / Next / Done controls. |
568
174
 
569
- - `focusedInput` is `null` or a window-relative `{ x, y, width, height }` layout (`FocusedInputLayout`) of the focused `TextInput`.
570
- - It measures while the keyboard is visible (on open, on height changes, and every 120 ms), and resets to `null` when the keyboard hides.
571
- - `refresh()` measures immediately and resolves with the new layout (or `null`). Call it after a layout change if you need an up-to-date value right away.
175
+ ### `KeyboardDismissButton`
572
176
 
573
- ```tsx
574
- import React from 'react';
575
- import { Text } from 'react-native';
576
- import { useFocusedInput } from 'react-native-keyboard-toolkit';
177
+ A `Pressable` (accepts `View` props) that dismisses the keyboard when pressed.
577
178
 
578
- export function FocusedFieldPosition(): React.JSX.Element {
579
- const { focusedInput, refresh } = useFocusedInput();
179
+ | Prop | Type | Default | Description |
180
+ | -------------------- | ------------ | -------------------- | ----------------------------------------- |
181
+ | `children` | `ReactNode` | required | Button content. |
182
+ | `onPress` | `() => void` | – | Runs before the keyboard is dismissed. |
183
+ | `dismissKeyboard` | `boolean` | `true` | Set `false` to skip `Keyboard.dismiss()`. |
184
+ | `accessibilityLabel` | `string` | `'Dismiss keyboard'` | Accessibility label. |
580
185
 
581
- return (
582
- <Text onPress={() => void refresh()}>
583
- {focusedInput
584
- ? `Focused field top: ${Math.round(focusedInput.y)} pt`
585
- : 'No measured focused field'}
586
- </Text>
587
- );
588
- }
589
- ```
186
+ ### `KeyboardProvider`
590
187
 
591
- ## Utility functions
188
+ Optional. Wrap your app in `<KeyboardProvider debug>` to log keyboard changes in development builds. Components and hooks work without it.
592
189
 
593
- These low-level helpers are what the components are built on. Most apps will not need them.
190
+ ### Behaviors
594
191
 
595
- | Function | Returns | Description |
596
- | ---------------------------------------------------------------------------------------------- | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
597
- | `getFocusedInput()` | `{ measureInWindow } \| null` | The currently focused native `TextInput` (from `TextInput.State`), or `null`. |
598
- | `measureFocusedInput(input?)` | `Promise<FocusedInputLayout \| null>` | Measures an input (default: the focused one) in window coordinates. |
599
- | `focusInput(ref)` | `boolean` | Calls `focus()` on a `RefObject<TextInputRef \| null>`; `false` if the ref is empty. |
600
- | `calculateKeyboardOverlap(viewport, keyboardHeight, keyboardTop?)` | `number` | How many points of `viewport` (`{ y, height }`) the keyboard covers. Uses `keyboardTop` (the keyboard's `screenY`) when given. |
601
- | `calculateScrollOffset(input, viewport, keyboardHeight, spacing, currentOffset, keyboardTop?)` | `{ offset: number; delta: number }` | The smallest scroll adjustment that brings `input` (`{ y, height }`) into the unobscured part of `viewport`. |
602
- | `isIOS`, `isAndroid` | `boolean` | Constants for the current `Platform.OS`. |
192
+ | Value | `KeyboardAwareScrollView` | `KeyboardAwareView` |
193
+ | ----------- | ----------------------------------------------------------------------- | -------------------------------------- |
194
+ | `'smart'` | Adds scroll space and scrolls only when the focused input is covered | Adjusts for keyboard overlap (default) |
195
+ | `'scroll'` | Same as `'smart'` | Same as `'smart'` |
196
+ | `'padding'` | Adds scroll space only, no focus scrolling | Same as `'smart'` |
197
+ | `'resize'` | Leaves resizing to the platform (uses automatic keyboard insets on iOS) | No adjustment |
198
+ | `'none'` | No adjustment | No adjustment |
603
199
 
604
- All coordinates passed to the calculation helpers must use the same window-coordinate system (for example values from `measureInWindow`).
200
+ ### Hooks
605
201
 
606
202
  ```tsx
607
- import React, { useRef, useState } from 'react';
608
- import { Text, TextInput, View } from 'react-native';
609
203
  import {
610
- focusInput,
611
- getFocusedInput,
612
- isIOS,
613
- measureFocusedInput,
614
- type FocusedInputLayout,
615
- type TextInputRef,
616
- } from 'react-native-keyboard-toolkit';
617
-
618
- export function UtilityDemo(): React.JSX.Element {
619
- const inputRef = useRef<TextInputRef>(null);
620
- const [layout, setLayout] = useState<FocusedInputLayout | null>(null);
621
-
622
- return (
623
- <View>
624
- <TextInput ref={inputRef} placeholder="Tap a button below" />
625
- <Text onPress={() => focusInput(inputRef)}>Focus the input</Text>
626
- <Text
627
- onPress={async () =>
628
- setLayout(await measureFocusedInput(getFocusedInput()))
629
- }
630
- >
631
- Measure the focused input ({isIOS ? 'iOS' : 'Android'})
632
- </Text>
633
- {layout && (
634
- <Text>
635
- y = {Math.round(layout.y)}, height = {Math.round(layout.height)}
636
- </Text>
637
- )}
638
- </View>
639
- );
640
- }
641
- ```
642
-
643
- ```ts
644
- import {
645
- calculateKeyboardOverlap,
646
- calculateScrollOffset,
204
+ useKeyboardState,
205
+ useKeyboardAnimation,
206
+ useFocusedInput,
647
207
  } from 'react-native-keyboard-toolkit';
648
208
 
649
- // A 600 pt tall viewport starting at y = 100, with a 300 pt keyboard whose
650
- // top edge is at y = 500:
651
- const viewport = { y: 100, height: 600 };
652
- const overlap = calculateKeyboardOverlap(viewport, 300, 500); // 200
653
-
654
- // An input at y = 520 (height 40) is hidden behind the keyboard. With 16 pt
655
- // of spacing and the content currently scrolled to 0, scroll by this much:
656
- const { delta, offset } = calculateScrollOffset(
657
- { y: 520, height: 40 },
658
- viewport,
659
- 300,
660
- 16,
661
- 0,
662
- 500,
663
- ); // delta = 76, offset = 76
664
- ```
665
-
666
- ## Complete React Native CLI example
209
+ const { isVisible, height, progress, animationDuration, screenY } =
210
+ useKeyboardState(); // `useKeyboard()` is an alias
667
211
 
668
- This walks through a fresh React Native CLI project from start to finish.
212
+ const { progress: anim, animationDuration: ms } = useKeyboardAnimation();
213
+ // `anim` is an Animated.Value (0 → 1); updates instantly if Reduce Motion is on
669
214
 
670
- **1. Create the app and install the library**
671
-
672
- ```sh
673
- npx @react-native-community/cli@latest init MyApp
674
- cd MyApp
675
- npm install react-native-keyboard-toolkit
215
+ const { focusedInput, refresh } = useFocusedInput();
216
+ // focusedInput: { x, y, width, height } in window coordinates, or null
676
217
  ```
677
218
 
678
- **2. Android window mode** – the template's `AndroidManifest.xml` already contains `android:windowSoftInputMode="adjustResize"` on `MainActivity`. Keep it.
219
+ | Hook | Returns |
220
+ | ------------------------ | ----------------------------------------------------------------------------------------- |
221
+ | `useKeyboardState()` | `{ isVisible, height, progress (0 \| 1), animationDuration, screenY? }` |
222
+ | `useKeyboardAnimation()` | `{ progress: Animated.Value, animationDuration }` |
223
+ | `useFocusedInput()` | `{ focusedInput, refresh }` – the focused input's layout, and a function to re-measure it |
679
224
 
680
- **3. iOS** – nothing to do for this library. If you have not installed pods yet, run `bundle install && cd ios && bundle exec pod install && cd ..` as usual for a new React Native project.
681
-
682
- **4. Replace `App.tsx`**
225
+ Example: animate a view with the keyboard.
683
226
 
684
227
  ```tsx
685
- import React, { useRef, useState } from 'react';
686
- import { Pressable, StyleSheet, Text, TextInput, View } from 'react-native';
687
- import {
688
- KeyboardAwareScrollView,
689
- KeyboardAwareView,
690
- KeyboardDismissButton,
691
- KeyboardProvider,
692
- KeyboardToolbar,
693
- useKeyboard,
694
- type TextInputRef,
695
- } from 'react-native-keyboard-toolkit';
696
-
697
- function KeyboardBadge(): React.JSX.Element {
698
- const { isVisible, height } = useKeyboard();
699
- return (
700
- <Text style={styles.badge}>
701
- {isVisible
702
- ? `Keyboard open (${Math.round(height)} pt)`
703
- : 'Keyboard closed'}
704
- </Text>
705
- );
706
- }
707
-
708
- function SignUpScreen(): React.JSX.Element {
709
- const nameRef = useRef<TextInputRef>(null);
710
- const emailRef = useRef<TextInputRef>(null);
711
- const bioRef = useRef<TextInputRef>(null);
712
- const [saved, setSaved] = useState(false);
713
-
714
- return (
715
- <View style={styles.screen}>
716
- <KeyboardBadge />
717
-
718
- <KeyboardAwareScrollView
719
- behavior="smart"
720
- keyboardSpacing={20}
721
- keyboardShouldPersistTaps="handled"
722
- keyboardDismissMode="interactive"
723
- contentContainerStyle={styles.content}
724
- >
725
- <Text style={styles.title}>Create your profile</Text>
726
-
727
- <TextInput
728
- ref={nameRef}
729
- style={styles.input}
730
- placeholder="Full name"
731
- returnKeyType="next"
732
- onSubmitEditing={() => emailRef.current?.focus()}
733
- />
734
- <TextInput
735
- ref={emailRef}
736
- style={styles.input}
737
- placeholder="Email"
738
- keyboardType="email-address"
739
- autoCapitalize="none"
740
- returnKeyType="next"
741
- onSubmitEditing={() => bioRef.current?.focus()}
742
- />
743
- <TextInput
744
- ref={bioRef}
745
- style={[styles.input, styles.multiline]}
746
- placeholder="A little about you"
747
- multiline
748
- textAlignVertical="top"
749
- />
750
-
751
- <Pressable style={styles.submit} onPress={() => setSaved(true)}>
752
- <Text style={styles.submitText}>
753
- {saved ? 'Saved' : 'Save profile'}
754
- </Text>
755
- </Pressable>
756
-
757
- <KeyboardDismissButton style={styles.dismiss}>
758
- <Text>Dismiss keyboard</Text>
759
- </KeyboardDismissButton>
760
- </KeyboardAwareScrollView>
761
-
762
- <KeyboardAwareView keyboardSpacing={0}>
763
- <KeyboardToolbar inputRefs={[nameRef, emailRef, bioRef]} />
764
- </KeyboardAwareView>
765
- </View>
766
- );
767
- }
768
-
769
- export default function App(): React.JSX.Element {
770
- return (
771
- <KeyboardProvider debug={__DEV__}>
772
- <SignUpScreen />
773
- </KeyboardProvider>
774
- );
775
- }
776
-
777
- const styles = StyleSheet.create({
778
- screen: { flex: 1, backgroundColor: '#ffffff', paddingTop: 48 },
779
- badge: { textAlign: 'center', color: '#475467', paddingVertical: 8 },
780
- content: { padding: 20, gap: 12 },
781
- title: { fontSize: 22, fontWeight: '700' },
782
- input: {
783
- borderWidth: 1,
784
- borderColor: '#d0d5dd',
785
- borderRadius: 8,
786
- paddingHorizontal: 12,
787
- paddingVertical: 10,
788
- fontSize: 16,
789
- },
790
- multiline: { minHeight: 96 },
791
- submit: {
792
- backgroundColor: '#155eef',
793
- borderRadius: 8,
794
- alignItems: 'center',
795
- paddingVertical: 12,
796
- },
797
- submitText: { color: '#ffffff', fontWeight: '600' },
798
- dismiss: { alignItems: 'center', padding: 12 },
799
- });
800
- ```
801
-
802
- **5. Run it**
803
-
804
- ```sh
805
- npx react-native start
806
- # in another terminal
807
- npx react-native run-android # or: npx react-native run-ios
808
- ```
809
-
810
- Prefer the safe area? Wrap the screen with the component from `react-native-safe-area-context` (installed separately) – the library does not depend on it and works the same inside it:
811
-
812
- ```tsx
813
- import React from 'react';
814
- import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
815
- import { KeyboardProvider } from 'react-native-keyboard-toolkit';
816
- import { Screens } from './Screens';
817
-
818
- export default function App(): React.JSX.Element {
819
- return (
820
- <SafeAreaProvider>
821
- <KeyboardProvider>
822
- <SafeAreaView style={{ flex: 1 }}>
823
- <Screens />
824
- </SafeAreaView>
825
- </KeyboardProvider>
826
- </SafeAreaProvider>
827
- );
828
- }
829
- ```
830
-
831
- ## TypeScript
832
-
833
- Type declarations ship with the package. The following types are exported from the package root:
834
-
835
- | Type | Description |
836
- | ------------------------------ | -------------------------------------------------------------------------------- |
837
- | `KeyboardBehavior` | `'smart' \| 'scroll' \| 'resize' \| 'padding' \| 'none'` |
838
- | `KeyboardState` | Shape returned by `useKeyboardState` / `useKeyboard`. |
839
- | `FocusedInputLayout` | `{ x, y, width, height }` returned by `useFocusedInput` / `measureFocusedInput`. |
840
- | `TextInputRef` | The `TextInput` instance type, for refs passed to the toolbar. |
841
- | `KeyboardAwareScrollViewProps` | Props of `KeyboardAwareScrollView`. |
842
- | `KeyboardAwareScrollViewRef` | Instance type of the `ScrollView` behind `KeyboardAwareScrollView`. |
843
- | `KeyboardAwareViewProps` | Props of `KeyboardAwareView`. |
844
- | `KeyboardAwareViewRef` | Instance type of the `View` behind `KeyboardAwareView`. |
845
- | `KeyboardToolbarProps` | Props of `KeyboardToolbar`. |
846
- | `KeyboardDismissButtonProps` | Props of `KeyboardDismissButton`. |
847
- | `KeyboardProviderProps` | Props of `KeyboardProvider`. |
848
-
849
- ```tsx
850
- import React from 'react';
851
- import {
852
- KeyboardAwareScrollView,
853
- type FocusedInputLayout,
854
- type KeyboardAwareScrollViewProps,
855
- type KeyboardBehavior,
856
- type KeyboardState,
857
- type KeyboardToolbarProps,
858
- } from 'react-native-keyboard-toolkit';
859
-
860
- const formBehavior: KeyboardBehavior = 'smart';
861
-
862
- const scrollProps: KeyboardAwareScrollViewProps = {
863
- behavior: formBehavior,
864
- keyboardSpacing: 18,
865
- keyboardShouldPersistTaps: 'handled',
866
- };
867
-
868
- const toolbarProps: KeyboardToolbarProps = { doneText: 'Close' };
869
-
870
- const hiddenKeyboard: KeyboardState = {
871
- isVisible: false,
872
- height: 0,
873
- progress: 0,
874
- animationDuration: 0,
875
- };
876
-
877
- const noLayout: FocusedInputLayout | null = null;
228
+ import { Animated } from 'react-native';
229
+ import { useKeyboardAnimation } from 'react-native-keyboard-toolkit';
878
230
 
879
- export function TypedForm(): React.JSX.Element {
231
+ function FadeWithKeyboard({ children }: { children: React.ReactNode }) {
232
+ const { progress } = useKeyboardAnimation();
880
233
  return (
881
- <KeyboardAwareScrollView
882
- {...scrollProps}
883
- accessibilityLabel={`${toolbarProps.doneText ?? ''} ${
884
- hiddenKeyboard.height
885
- } ${noLayout?.x ?? 0}`}
886
- />
234
+ <Animated.View style={{ opacity: progress }}>{children}</Animated.View>
887
235
  );
888
236
  }
889
237
  ```
890
238
 
891
- ## API reference
892
-
893
- ### Components
894
-
895
- | Export | Props | Ref instance |
896
- | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- |
897
- | `KeyboardAwareScrollView` | `ScrollViewProps` + `behavior`, `keyboardSpacing`, `enabled` | `KeyboardAwareScrollViewRef` |
898
- | `KeyboardAwareView` | `ViewProps` + `behavior`, `keyboardSpacing`, `enabled` | `KeyboardAwareViewRef` |
899
- | `KeyboardToolbar` | `ViewProps` + `inputRefs`, `onFocusedInputChange`, `onDone`, `doneText`, `alwaysVisible`, `children`, `focusedInputIndex` (deprecated) | – |
900
- | `KeyboardDismissButton` | `ViewProps` + `children`, `onPress`, `dismissKeyboard`, `accessibilityLabel`, `style` | – |
901
- | `KeyboardProvider` | `children`, `debug` | – |
902
-
903
- ### Hooks
904
-
905
- | Export | Signature |
906
- | ---------------------- | -------------------------------------------------------------------------------------------------------- |
907
- | `useKeyboardState` | `() => KeyboardState` |
908
- | `useKeyboard` | Alias of `useKeyboardState`. |
909
- | `useKeyboardAnimation` | `() => { progress: Animated.Value; animationDuration: number }` |
910
- | `useFocusedInput` | `() => { focusedInput: FocusedInputLayout \| null; refresh: () => Promise<FocusedInputLayout \| null> }` |
911
-
912
- ### Functions and constants
913
-
914
- | Export | Signature |
915
- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
916
- | `getFocusedInput` | `() => Pick<TextInputRef, 'measureInWindow'> \| null` |
917
- | `measureFocusedInput` | `(input?: Pick<TextInputRef, 'measureInWindow'> \| null) => Promise<FocusedInputLayout \| null>` |
918
- | `focusInput` | `(input: RefObject<TextInputRef \| null>) => boolean` |
919
- | `calculateKeyboardOverlap` | `(viewport: { y: number; height: number }, keyboardHeight: number, keyboardTop?: number) => number` |
920
- | `calculateScrollOffset` | `(input: { y: number; height: number }, viewport: { y: number; height: number }, keyboardHeight: number, spacing: number, currentOffset: number, keyboardTop?: number) => { offset: number; delta: number }` |
921
- | `isIOS`, `isAndroid` | `boolean` |
922
-
923
- ## How it works
924
-
925
- - **One shared subscription.** The first component or hook that subscribes starts the native keyboard listeners; the last one to unmount stops them. If the keyboard is already open when the first subscriber mounts, the state is initialised from `Keyboard.metrics()`.
926
- - **iOS** listens to `keyboardWillChangeFrame`. iOS reports a hidden keyboard as a frame moved _below_ the screen (with its height unchanged), so the keyboard is considered visible only while its top edge is above the bottom of the screen.
927
- - **Android** listens to `keyboardDidShow` and `keyboardDidHide`.
928
- - **Overlap, not height.** `KeyboardAwareScrollView` and `KeyboardAwareView` measure themselves with `measureInWindow` and intersect that rectangle with the keyboard frame. The reported keyboard height can therefore be non-zero while the measured overlap is `0` (for example when Android `adjustResize` already shrank the view).
929
- - **Focused input.** The focused field comes from `TextInput.State.currentlyFocusedInput()` and is measured with `measureInWindow`.
930
- - Floating or split keyboards, third-party keyboards, and modal windows can report different frames. Measurement-based adjustment is best-effort; verify the devices and configurations you ship.
931
-
932
- ## Troubleshooting
933
-
934
- **An input is still covered by the keyboard.**
935
- Use `KeyboardAwareScrollView`, confirm the focused native `TextInput` is _inside_ that scroll view, and check that no ancestor clips its content. `behavior` must be `'smart'` or `'scroll'` and `enabled` must be `true` for focus scrolling.
936
-
937
- **The focused field does not scroll.**
938
- Make sure it is a native React Native `TextInput` (not an unmeasurable third-party wrapper) that has completed layout. If a custom wrapper delays focus, call `refresh()` from [`useFocusedInput`](#usefocusedinput) after layout settles.
939
-
940
- **A bottom button is not reachable.**
941
- Keep the button _inside_ the aware scroll view so it is part of the scroll range, and set `keyboardShouldPersistTaps="handled"` so the first tap is not swallowed by dismissing the keyboard.
942
-
943
- **The layout moves twice / there is too much space.**
944
- Avoid stacking keyboard-padding wrappers (an app-level `KeyboardAvoidingView`, keyboard insets, or multiple aware components) around the same content. When a `KeyboardAwareView` wraps a `KeyboardAwareScrollView`, the outer view lifts the content and the inner scroll view then measures no overlap. If two components still both adjust, disable one with `enabled={false}` or `behavior="none"`.
945
-
946
- **The keyboard height is non-zero but the view does not add the same amount.**
947
- This is expected. Components add padding for the _measured overlap_, which is `0` when Android has already resized the window (`adjustResize`).
948
-
949
- **Android: content moves twice, or nothing moves.**
950
- Use `android:windowSoftInputMode="adjustResize"` on your activity. Edge-to-edge configurations and some OEM keyboards report coordinates relative to a different window origin; test on your target devices. See [`docs/android.md`](docs/android.md).
951
-
952
- **Keyboard height is `0` or unexpected.**
953
- A hardware keyboard may not open a software keyboard. Floating and split keyboards occupy only part of the screen. Heights can vary with navigation bars and OS versions.
954
-
955
- **`KeyboardToolbar` controls are disabled.**
956
- Pass refs to _mounted_ `TextInput`s in the intended order. A ref must point at a mounted input for navigation to work. The toolbar is not rendered while the keyboard is hidden unless `alwaysVisible` is set.
239
+ ### Utilities and types
957
240
 
958
- **The toolbar is not above the keyboard.**
959
- `KeyboardToolbar` is a normal in-layout view, not a native input accessory. Place it at the bottom of your screen layout, ideally inside a `KeyboardAwareView` as shown in the examples.
241
+ - `focusInput(ref)` – focuses a `TextInput` ref; returns `false` if the ref is empty.
242
+ - `getFocusedInput()` / `measureFocusedInput(input?)` – get or measure the focused input.
243
+ - `calculateKeyboardOverlap(viewport, keyboardHeight, keyboardTop?)` and `calculateScrollOffset(input, viewport, keyboardHeight, spacing, currentOffset, keyboardTop?)` – the layout math used internally.
244
+ - `isIOS`, `isAndroid` – platform flags.
245
+ - Types: `KeyboardState`, `KeyboardBehavior`, `TextInputRef`, `FocusedInputLayout`, `KeyboardAwareScrollViewProps`, `KeyboardAwareScrollViewRef`, `KeyboardAwareViewProps`, `KeyboardAwareViewRef`, `KeyboardToolbarProps`, `KeyboardDismissButtonProps`, `KeyboardProviderProps`.
960
246
 
961
- **The keyboard hides a field inside a modal.**
962
- Place the aware scroll view inside the modal content and test on the target platform; modal window frames and insets differ from the main screen.
247
+ ## Tips
963
248
 
964
- **`ref` on `KeyboardAwareScrollView` is `null`.**
965
- The ref is set after mount. Read `ref.current` in event handlers or effects, not during render.
249
+ - Use React Native `TextInput` fields inside `KeyboardAwareScrollView`. Custom input wrappers must forward refs and support native measurement.
250
+ - Use only one keyboard-spacing component per region to avoid adding space twice.
251
+ - Keyboard coordinates can differ across devices, edge-to-edge modes, and modals — test on the devices you support.
966
252
 
967
- **State does not update.**
968
- Verify that focusing the input actually opens the _software_ keyboard (not available on some simulators with a connected hardware keyboard: toggle _I/O → Keyboard → Toggle Software Keyboard_ on the iOS simulator).
969
-
970
- **Using the hooks in Jest tests.**
971
- The hooks and components render under React Native's Jest preset without extra setup. To simulate keyboard events, spy on `Keyboard.addListener`, capture the listener, and call it with a `KeyboardEvent`-shaped object (see the repository's `tests/` folder for examples).
972
-
973
- For more, see the platform notes: [`docs/ios.md`](docs/ios.md) and [`docs/android.md`](docs/android.md).
974
-
975
- ## Example project
976
-
977
- The repository contains a React Native CLI host app that exercises every component and hook against the local source (`demo/App.tsx`, mounted from `App.tsx`). It includes a keyboard-state panel, behavior pickers, a long form, a compact `KeyboardAwareView` layout, toolbar navigation, and utility buttons.
978
-
979
- ```sh
980
- git clone https://github.com/DenishDabhi/react-native-keyboard-toolkit.git
981
- cd react-native-keyboard-toolkit
982
- npm ci
983
-
984
- # iOS only, first time
985
- bundle install
986
- cd ios && bundle exec pod install && cd ..
987
-
988
- npm start # Metro
989
- npm run android # in another terminal
990
- npm run ios # in another terminal
991
- ```
992
-
993
- The demo uses `react-native-safe-area-context` (a dev dependency of this repository, not of the library). The native projects are still named `MyKeywordHandler` (the module name registered in `app.json`); the name shown on the device is "Keyboard Toolkit Demo".
994
-
995
- ## Contributing
996
-
997
- Contributions are welcome. See [`CONTRIBUTING.md`](CONTRIBUTING.md) and the [Code of Conduct](CODE_OF_CONDUCT.md).
998
-
999
- ```sh
1000
- npm ci
1001
- npm run validate # typecheck, lint, prettier check, tests, build, npm pack --dry-run
1002
- ```
1003
-
1004
- Individual scripts: `npm run typecheck`, `npm run lint`, `npm run format:check`, `npm test`, `npm run build`. Please add tests for behavior changes and update this README when the public API changes. Report security issues as described in [`SECURITY.md`](SECURITY.md).
253
+ More guides: [Installation](docs/installation.md) · [API](docs/api.md) · [Android](docs/android.md) · [iOS](docs/ios.md) · [Troubleshooting](docs/troubleshooting.md)
1005
254
 
1006
255
  ## License
1007
256
 
1008
257
  [MIT](LICENSE) © Denish Dabhi
1009
-
1010
- ## Links
1011
-
1012
- - GitHub: <https://github.com/DenishDabhi/react-native-keyboard-toolkit>
1013
- - Issues: <https://github.com/DenishDabhi/react-native-keyboard-toolkit/issues>
1014
- - npm: <https://www.npmjs.com/package/react-native-keyboard-toolkit>
1015
- - Changelog: [`CHANGELOG.md`](CHANGELOG.md)