react-native-keyboard-toolkit 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/CHANGELOG.md +13 -0
- package/README.md +155 -883
- package/dist/components/KeyboardAwareScrollView.js +2 -2
- package/dist/components/KeyboardAwareView.d.ts +1 -1
- package/dist/components/KeyboardAwareView.js +61 -12
- package/dist/hooks/useKeyboardState.d.ts +3 -1
- package/dist/hooks/useKeyboardState.js +6 -2
- package/dist/types/index.d.ts +5 -1
- package/dist/utils/keyboardEvents.d.ts +2 -0
- package/dist/utils/keyboardEvents.js +46 -13
- package/dist/utils/scrollManager.d.ts +2 -0
- package/dist/utils/scrollManager.js +5 -0
- package/docs/android.md +5 -0
- package/docs/api.md +14 -4
- package/docs/ios.md +5 -0
- package/docs/troubleshooting.md +35 -0
- package/package.json +5 -5
- package/src/components/KeyboardAwareScrollView.tsx +2 -1
- package/src/components/KeyboardAwareView.tsx +80 -19
- package/src/hooks/useKeyboardState.ts +10 -3
- package/src/types/index.ts +5 -1
- package/src/utils/keyboardEvents.ts +57 -15
- package/src/utils/scrollManager.ts +12 -0
package/README.md
CHANGED
|
@@ -2,78 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/react-native-keyboard-toolkit)
|
|
4
4
|
[](LICENSE)
|
|
5
|
-
|
|
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
|
+

|
|
37
6
|
|
|
38
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,266 @@ 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
|
-
|
|
27
|
+
**Requirements:** React `>=18.2.0`, React Native `>=0.71.0`. No iOS setup is needed.
|
|
112
28
|
|
|
113
|
-
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
On iOS there is nothing to configure.
|
|
134
|
-
|
|
135
|
-
### Expo
|
|
38
|
+
## Quick start
|
|
136
39
|
|
|
137
|
-
|
|
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()
|
|
47
|
+
export function ContactForm() {
|
|
219
48
|
return (
|
|
220
49
|
<KeyboardAwareScrollView
|
|
221
|
-
behavior="smart"
|
|
222
|
-
keyboardSpacing={20}
|
|
223
50
|
keyboardShouldPersistTaps="handled"
|
|
224
51
|
keyboardDismissMode="interactive"
|
|
225
|
-
|
|
52
|
+
behavior="smart"
|
|
53
|
+
keyboardSpacing={100}
|
|
54
|
+
contentContainerStyle={{
|
|
55
|
+
flexGrow: 1,
|
|
56
|
+
}}
|
|
226
57
|
>
|
|
227
58
|
<TextInput placeholder="Name" />
|
|
228
59
|
<TextInput placeholder="Email" keyboardType="email-address" />
|
|
229
60
|
<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
61
|
</KeyboardAwareScrollView>
|
|
261
62
|
);
|
|
262
63
|
}
|
|
263
64
|
```
|
|
264
65
|
|
|
265
|
-
|
|
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:
|
|
66
|
+
### Sticky submit button
|
|
276
67
|
|
|
277
|
-
|
|
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. |
|
|
290
|
-
|
|
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.
|
|
68
|
+
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
69
|
|
|
293
70
|
```tsx
|
|
294
71
|
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';
|
|
72
|
+
import { Pressable, Text, TextInput, View } from 'react-native';
|
|
340
73
|
import {
|
|
341
74
|
KeyboardAwareScrollView,
|
|
342
75
|
KeyboardAwareView,
|
|
343
|
-
KeyboardToolbar,
|
|
344
|
-
type TextInputRef,
|
|
345
76
|
} from 'react-native-keyboard-toolkit';
|
|
346
77
|
|
|
347
|
-
export function
|
|
348
|
-
const emailRef = useRef<TextInputRef>(null);
|
|
349
|
-
const passwordRef = useRef<TextInputRef>(null);
|
|
350
|
-
|
|
78
|
+
export function StickyForm() {
|
|
351
79
|
return (
|
|
352
|
-
<View style={
|
|
80
|
+
<View style={{ flex: 1 }}>
|
|
353
81
|
<KeyboardAwareScrollView
|
|
354
82
|
keyboardShouldPersistTaps="handled"
|
|
355
|
-
|
|
83
|
+
keyboardSpacing={100} // leave room for the footer
|
|
84
|
+
contentContainerStyle={{ padding: 20, paddingBottom: 96 }}
|
|
356
85
|
>
|
|
357
|
-
<TextInput
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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
|
-
/>
|
|
86
|
+
<TextInput placeholder="Name" />
|
|
87
|
+
<TextInput placeholder="Email" keyboardType="email-address" />
|
|
88
|
+
<TextInput placeholder="Company" />
|
|
89
|
+
<TextInput placeholder="Message" multiline />
|
|
371
90
|
</KeyboardAwareScrollView>
|
|
372
91
|
|
|
373
|
-
<KeyboardAwareView
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
92
|
+
<KeyboardAwareView
|
|
93
|
+
keyboardSpacing={12}
|
|
94
|
+
style={{
|
|
95
|
+
position: 'absolute',
|
|
96
|
+
left: 0,
|
|
97
|
+
right: 0,
|
|
98
|
+
bottom: 0,
|
|
99
|
+
padding: 16,
|
|
100
|
+
backgroundColor: 'white',
|
|
101
|
+
}}
|
|
102
|
+
>
|
|
103
|
+
<Pressable onPress={() => console.log('Submit')}>
|
|
104
|
+
<Text>Submit</Text>
|
|
105
|
+
</Pressable>
|
|
383
106
|
</KeyboardAwareView>
|
|
384
107
|
</View>
|
|
385
108
|
);
|
|
386
109
|
}
|
|
387
|
-
|
|
388
|
-
const styles = StyleSheet.create({
|
|
389
|
-
screen: { flex: 1 },
|
|
390
|
-
content: { padding: 20, gap: 12 },
|
|
391
|
-
});
|
|
392
110
|
```
|
|
393
111
|
|
|
394
|
-
|
|
112
|
+
Set the scroll view's `keyboardSpacing` to roughly your footer height so focused inputs are not hidden behind it.
|
|
113
|
+
|
|
114
|
+
### Previous / Next / Done toolbar
|
|
115
|
+
|
|
116
|
+
Pass your input refs in navigation order. The toolbar renders only while the keyboard is visible.
|
|
395
117
|
|
|
396
118
|
```tsx
|
|
397
|
-
import React from 'react';
|
|
398
|
-
import {
|
|
119
|
+
import React, { useRef } from 'react';
|
|
120
|
+
import { TextInput, View } from 'react-native';
|
|
399
121
|
import {
|
|
400
|
-
|
|
122
|
+
KeyboardAwareScrollView,
|
|
401
123
|
KeyboardToolbar,
|
|
124
|
+
type TextInputRef,
|
|
402
125
|
} from 'react-native-keyboard-toolkit';
|
|
403
126
|
|
|
404
|
-
export function
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
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`.
|
|
428
|
-
|
|
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
|
-
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. |
|
|
127
|
+
export function SignUpForm() {
|
|
128
|
+
const name = useRef<TextInputRef>(null);
|
|
129
|
+
const email = useRef<TextInputRef>(null);
|
|
130
|
+
const password = useRef<TextInputRef>(null);
|
|
455
131
|
|
|
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
132
|
return (
|
|
463
|
-
<
|
|
464
|
-
<
|
|
465
|
-
|
|
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();
|
|
133
|
+
<View style={{ flex: 1 }}>
|
|
134
|
+
<KeyboardAwareScrollView keyboardShouldPersistTaps="handled">
|
|
135
|
+
<TextInput ref={name} placeholder="Name" />
|
|
136
|
+
<TextInput ref={email} placeholder="Email" />
|
|
137
|
+
<TextInput ref={password} placeholder="Password" secureTextEntry />
|
|
138
|
+
</KeyboardAwareScrollView>
|
|
497
139
|
|
|
498
|
-
|
|
499
|
-
<View>
|
|
500
|
-
<Text>{isVisible ? `Keyboard: ${height} pt` : 'Keyboard hidden'}</Text>
|
|
501
|
-
{screenY !== undefined && <Text>Top edge: {screenY} pt</Text>}
|
|
140
|
+
<KeyboardToolbar inputRefs={[name, email, password]} />
|
|
502
141
|
</View>
|
|
503
142
|
);
|
|
504
143
|
}
|
|
505
144
|
```
|
|
506
145
|
|
|
507
|
-
|
|
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:
|
|
510
|
-
|
|
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
|
-
```
|
|
530
|
-
|
|
531
|
-
### `useKeyboardAnimation`
|
|
532
|
-
|
|
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.
|
|
534
|
-
|
|
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`.
|
|
536
|
-
|
|
537
|
-
```tsx
|
|
538
|
-
import React from 'react';
|
|
539
|
-
import { Animated, Text } from 'react-native';
|
|
540
|
-
import { useKeyboardAnimation } from 'react-native-keyboard-toolkit';
|
|
541
|
-
|
|
542
|
-
export function AnimatedKeyboardLabel(): React.JSX.Element {
|
|
543
|
-
const { progress } = useKeyboardAnimation();
|
|
544
|
-
|
|
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
|
-
```
|
|
564
|
-
|
|
565
|
-
### `useFocusedInput`
|
|
566
|
-
|
|
567
|
-
Returns `{ focusedInput, refresh }`.
|
|
146
|
+
## API
|
|
568
147
|
|
|
569
|
-
|
|
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.
|
|
572
|
-
|
|
573
|
-
```tsx
|
|
574
|
-
import React from 'react';
|
|
575
|
-
import { Text } from 'react-native';
|
|
576
|
-
import { useFocusedInput } from 'react-native-keyboard-toolkit';
|
|
577
|
-
|
|
578
|
-
export function FocusedFieldPosition(): React.JSX.Element {
|
|
579
|
-
const { focusedInput, refresh } = useFocusedInput();
|
|
580
|
-
|
|
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
|
-
```
|
|
148
|
+
### `KeyboardAwareScrollView`
|
|
590
149
|
|
|
591
|
-
|
|
150
|
+
Accepts all `ScrollView` props, forwards its ref to the `ScrollView`, and adds:
|
|
592
151
|
|
|
593
|
-
|
|
152
|
+
| Prop | Type | Default | Description |
|
|
153
|
+
| ----------------- | -------------------------------------------------------- | --------- | -------------------------------------------------------------------------------------- |
|
|
154
|
+
| `behavior` | `'smart' \| 'scroll' \| 'resize' \| 'padding' \| 'none'` | `'smart'` | See [Behaviors](#behaviors). |
|
|
155
|
+
| `keyboardSpacing` | `number` | `16` | Gap (points) between the focused input and the keyboard. Negative values become `0`. |
|
|
156
|
+
| `enabled` | `boolean` | `true` | Turns keyboard padding and focus scrolling on or off. |
|
|
157
|
+
| `isFocused` | `boolean` | – | Optional navigation focus state; resets on blur and resyncs keyboard metrics on focus. |
|
|
594
158
|
|
|
595
|
-
|
|
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`. |
|
|
159
|
+
### `KeyboardAwareView`
|
|
603
160
|
|
|
604
|
-
|
|
161
|
+
Accepts all `View` props, forwards its ref to the native view, and supports `behavior`, `keyboardSpacing` (default `16`), `enabled`, and optional `isFocused` navigation state.
|
|
605
162
|
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
import {
|
|
610
|
-
focusInput,
|
|
611
|
-
getFocusedInput,
|
|
612
|
-
isIOS,
|
|
613
|
-
measureFocusedInput,
|
|
614
|
-
type FocusedInputLayout,
|
|
615
|
-
type TextInputRef,
|
|
616
|
-
} from 'react-native-keyboard-toolkit';
|
|
163
|
+
- **Normal layout:** adds bottom padding equal to the part of the view covered by the keyboard.
|
|
164
|
+
- **`position: 'absolute'`:** animates the view up so it clears the keyboard plus `keyboardSpacing`.
|
|
165
|
+
- It does not scroll to reveal hidden children.
|
|
617
166
|
|
|
618
|
-
|
|
619
|
-
const inputRef = useRef<TextInputRef>(null);
|
|
620
|
-
const [layout, setLayout] = useState<FocusedInputLayout | null>(null);
|
|
167
|
+
### `KeyboardToolbar`
|
|
621
168
|
|
|
622
|
-
|
|
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
|
-
```
|
|
169
|
+
Accepts all `View` props plus:
|
|
642
170
|
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
171
|
+
| Prop | Type | Default | Description |
|
|
172
|
+
| ---------------------- | ----------------------------------- | -------- | ---------------------------------------------------------------------------------------------------- |
|
|
173
|
+
| `inputRefs` | `RefObject<TextInputRef \| null>[]` | `[]` | Inputs in navigation order. The focused input is detected automatically; unmounted refs are skipped. |
|
|
174
|
+
| `onFocusedInputChange` | `(index: number) => void` | – | Called when the focused input changes (`-1` = none of `inputRefs`). |
|
|
175
|
+
| `onDone` | `() => void` | – | Called after Done dismisses the keyboard. |
|
|
176
|
+
| `doneText` | `string` | `'Done'` | Label of the Done button. |
|
|
177
|
+
| `alwaysVisible` | `boolean` | `false` | Show the toolbar even when the keyboard is hidden. |
|
|
178
|
+
| `children` | `ReactNode` | – | Replaces the built-in Previous / Next / Done controls. |
|
|
648
179
|
|
|
649
|
-
|
|
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
|
-
```
|
|
180
|
+
### `KeyboardDismissButton`
|
|
665
181
|
|
|
666
|
-
|
|
182
|
+
A `Pressable` (accepts `View` props) that dismisses the keyboard when pressed.
|
|
667
183
|
|
|
668
|
-
|
|
184
|
+
| Prop | Type | Default | Description |
|
|
185
|
+
| -------------------- | ------------ | -------------------- | ----------------------------------------- |
|
|
186
|
+
| `children` | `ReactNode` | required | Button content. |
|
|
187
|
+
| `onPress` | `() => void` | – | Runs before the keyboard is dismissed. |
|
|
188
|
+
| `dismissKeyboard` | `boolean` | `true` | Set `false` to skip `Keyboard.dismiss()`. |
|
|
189
|
+
| `accessibilityLabel` | `string` | `'Dismiss keyboard'` | Accessibility label. |
|
|
669
190
|
|
|
670
|
-
|
|
191
|
+
### `KeyboardProvider`
|
|
671
192
|
|
|
672
|
-
|
|
673
|
-
npx @react-native-community/cli@latest init MyApp
|
|
674
|
-
cd MyApp
|
|
675
|
-
npm install react-native-keyboard-toolkit
|
|
676
|
-
```
|
|
193
|
+
Optional. Wrap your app in `<KeyboardProvider debug>` to log keyboard changes in development builds. Components and hooks work without it.
|
|
677
194
|
|
|
678
|
-
|
|
195
|
+
### Behaviors
|
|
679
196
|
|
|
680
|
-
|
|
197
|
+
| Value | `KeyboardAwareScrollView` | `KeyboardAwareView` |
|
|
198
|
+
| ----------- | ----------------------------------------------------------------------- | -------------------------------------- |
|
|
199
|
+
| `'smart'` | Adds scroll space and scrolls only when the focused input is covered | Adjusts for keyboard overlap (default) |
|
|
200
|
+
| `'scroll'` | Same as `'smart'` | Same as `'smart'` |
|
|
201
|
+
| `'padding'` | Adds scroll space only, no focus scrolling | Same as `'smart'` |
|
|
202
|
+
| `'resize'` | Leaves resizing to the platform (uses automatic keyboard insets on iOS) | No adjustment |
|
|
203
|
+
| `'none'` | No adjustment | No adjustment |
|
|
681
204
|
|
|
682
|
-
|
|
205
|
+
### Hooks
|
|
683
206
|
|
|
684
207
|
```tsx
|
|
685
|
-
import React, { useRef, useState } from 'react';
|
|
686
|
-
import { Pressable, StyleSheet, Text, TextInput, View } from 'react-native';
|
|
687
208
|
import {
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
KeyboardProvider,
|
|
692
|
-
KeyboardToolbar,
|
|
693
|
-
useKeyboard,
|
|
694
|
-
type TextInputRef,
|
|
209
|
+
useKeyboardState,
|
|
210
|
+
useKeyboardAnimation,
|
|
211
|
+
useFocusedInput,
|
|
695
212
|
} from 'react-native-keyboard-toolkit';
|
|
696
213
|
|
|
697
|
-
|
|
698
|
-
|
|
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 />
|
|
214
|
+
const { isVisible, height, progress, animationDuration, screenY } =
|
|
215
|
+
useKeyboardState(); // `useKeyboard()` is an alias
|
|
717
216
|
|
|
718
|
-
|
|
719
|
-
|
|
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
|
-
}
|
|
217
|
+
const { progress: anim, animationDuration: ms } = useKeyboardAnimation();
|
|
218
|
+
// `anim` is an Animated.Value (0 → 1); updates instantly if Reduce Motion is on
|
|
768
219
|
|
|
769
|
-
|
|
770
|
-
|
|
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
|
-
});
|
|
220
|
+
const { focusedInput, refresh } = useFocusedInput();
|
|
221
|
+
// focusedInput: { x, y, width, height } in window coordinates, or null
|
|
800
222
|
```
|
|
801
223
|
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
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:
|
|
224
|
+
For a screen that stays mounted in a navigation stack, pass its focus state to
|
|
225
|
+
both keyboard-aware components. This does not add a React Navigation dependency
|
|
226
|
+
to the toolkit:
|
|
811
227
|
|
|
812
228
|
```tsx
|
|
813
|
-
|
|
814
|
-
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
|
|
815
|
-
import { KeyboardProvider } from 'react-native-keyboard-toolkit';
|
|
816
|
-
import { Screens } from './Screens';
|
|
229
|
+
const isFocused = useIsFocused(); // from @react-navigation/native
|
|
817
230
|
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
<SafeAreaView style={{ flex: 1 }}>
|
|
823
|
-
<Screens />
|
|
824
|
-
</SafeAreaView>
|
|
825
|
-
</KeyboardProvider>
|
|
826
|
-
</SafeAreaProvider>
|
|
827
|
-
);
|
|
828
|
-
}
|
|
231
|
+
<KeyboardAwareScrollView isFocused={isFocused}>...</KeyboardAwareScrollView>
|
|
232
|
+
<KeyboardAwareView isFocused={isFocused} style={footerStyle}>
|
|
233
|
+
...
|
|
234
|
+
</KeyboardAwareView>
|
|
829
235
|
```
|
|
830
236
|
|
|
831
|
-
|
|
237
|
+
| Hook | Returns |
|
|
238
|
+
| ---------------------------------- | ----------------------------------------------------------------------------------------- |
|
|
239
|
+
| `useKeyboardState({ isFocused? })` | `{ isVisible, height, progress (0 \| 1), animationDuration, screenY? }` |
|
|
240
|
+
| `useKeyboardAnimation()` | `{ progress: Animated.Value, animationDuration }` |
|
|
241
|
+
| `useFocusedInput()` | `{ focusedInput, refresh }` – the focused input's layout, and a function to re-measure it |
|
|
832
242
|
|
|
833
|
-
|
|
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`. |
|
|
243
|
+
Example: animate a view with the keyboard.
|
|
848
244
|
|
|
849
245
|
```tsx
|
|
850
|
-
import
|
|
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;
|
|
246
|
+
import { Animated } from 'react-native';
|
|
247
|
+
import { useKeyboardAnimation } from 'react-native-keyboard-toolkit';
|
|
878
248
|
|
|
879
|
-
|
|
249
|
+
function FadeWithKeyboard({ children }: { children: React.ReactNode }) {
|
|
250
|
+
const { progress } = useKeyboardAnimation();
|
|
880
251
|
return (
|
|
881
|
-
<
|
|
882
|
-
{...scrollProps}
|
|
883
|
-
accessibilityLabel={`${toolbarProps.doneText ?? ''} ${
|
|
884
|
-
hiddenKeyboard.height
|
|
885
|
-
} ${noLayout?.x ?? 0}`}
|
|
886
|
-
/>
|
|
252
|
+
<Animated.View style={{ opacity: progress }}>{children}</Animated.View>
|
|
887
253
|
);
|
|
888
254
|
}
|
|
889
255
|
```
|
|
890
256
|
|
|
891
|
-
|
|
257
|
+
### Utilities and types
|
|
892
258
|
|
|
893
|
-
|
|
259
|
+
- `focusInput(ref)` – focuses a `TextInput` ref; returns `false` if the ref is empty.
|
|
260
|
+
- `getFocusedInput()` / `measureFocusedInput(input?)` – get or measure the focused input.
|
|
261
|
+
- `calculateKeyboardOverlap(viewport, keyboardHeight, keyboardTop?)` and `calculateScrollOffset(input, viewport, keyboardHeight, spacing, currentOffset, keyboardTop?)` – the layout math used internally.
|
|
262
|
+
- `isIOS`, `isAndroid` – platform flags.
|
|
263
|
+
- Types: `KeyboardState`, `KeyboardBehavior`, `TextInputRef`, `FocusedInputLayout`, `KeyboardAwareScrollViewProps`, `KeyboardAwareScrollViewRef`, `KeyboardAwareViewProps`, `KeyboardAwareViewRef`, `KeyboardToolbarProps`, `KeyboardDismissButtonProps`, `KeyboardProviderProps`.
|
|
894
264
|
|
|
895
|
-
|
|
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
|
|
265
|
+
## Tips
|
|
904
266
|
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
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.
|
|
267
|
+
- Use React Native `TextInput` fields inside `KeyboardAwareScrollView`. Custom input wrappers must forward refs and support native measurement.
|
|
268
|
+
- Use only one keyboard-spacing component per region to avoid adding space twice.
|
|
269
|
+
- Keyboard coordinates can differ across devices, edge-to-edge modes, and modals — test on the devices you support.
|
|
931
270
|
|
|
932
271
|
## Troubleshooting
|
|
933
272
|
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
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.
|
|
957
|
-
|
|
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.
|
|
960
|
-
|
|
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.
|
|
273
|
+
The stale keyboard-height / blank-space issue after navigating away with the
|
|
274
|
+
keyboard open and returning is fixed in **1.0.2**. For stack screens that stay
|
|
275
|
+
mounted, pass `useIsFocused()` to both `KeyboardAwareScrollView` and
|
|
276
|
+
`KeyboardAwareView` so the state is reconciled on focus.
|
|
963
277
|
|
|
964
|
-
|
|
965
|
-
|
|
278
|
+
Manual check: focus a Screen A field, select **Go to Screen B**, then use
|
|
279
|
+
Screen B's **Back** button. Repeat with a fast transition and with the app
|
|
280
|
+
backgrounded while the keyboard is open. Screen A should return with no
|
|
281
|
+
keyboard-sized gap and its sticky button at the original position.
|
|
966
282
|
|
|
967
|
-
|
|
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).
|
|
283
|
+
More guides: [Installation](docs/installation.md) · [API](docs/api.md) · [Android](docs/android.md) · [iOS](docs/ios.md) · [Troubleshooting](docs/troubleshooting.md)
|
|
1005
284
|
|
|
1006
285
|
## License
|
|
1007
286
|
|
|
1008
287
|
[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)
|