react-native-keyboard-toolkit 0.0.0-stage → 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.
Files changed (70) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/LICENSE +21 -0
  3. package/README.md +256 -2
  4. package/dist/components/KeyboardAwareScrollView.d.ts +6 -0
  5. package/dist/components/KeyboardAwareScrollView.js +137 -0
  6. package/dist/components/KeyboardAwareView.d.ts +6 -0
  7. package/dist/components/KeyboardAwareView.js +112 -0
  8. package/dist/components/KeyboardDismissButton.d.ts +5 -0
  9. package/dist/components/KeyboardDismissButton.js +16 -0
  10. package/dist/components/KeyboardToolbar.d.ts +5 -0
  11. package/dist/components/KeyboardToolbar.js +109 -0
  12. package/dist/components/index.d.ts +4 -0
  13. package/dist/components/index.js +11 -0
  14. package/dist/context/KeyboardContext.d.ts +6 -0
  15. package/dist/context/KeyboardContext.js +27 -0
  16. package/dist/context/index.d.ts +1 -0
  17. package/dist/context/index.js +5 -0
  18. package/dist/hooks/index.d.ts +4 -0
  19. package/dist/hooks/index.js +11 -0
  20. package/dist/hooks/useFocusedInput.d.ts +6 -0
  21. package/dist/hooks/useFocusedInput.js +42 -0
  22. package/dist/hooks/useKeyboard.d.ts +1 -0
  23. package/dist/hooks/useKeyboard.js +5 -0
  24. package/dist/hooks/useKeyboardAnimation.d.ts +6 -0
  25. package/dist/hooks/useKeyboardAnimation.js +43 -0
  26. package/dist/hooks/useKeyboardState.d.ts +2 -0
  27. package/dist/hooks/useKeyboardState.js +9 -0
  28. package/dist/index.d.ts +5 -0
  29. package/dist/index.js +23 -0
  30. package/dist/types/index.d.ts +69 -0
  31. package/dist/types/index.js +2 -0
  32. package/dist/utils/constants.d.ts +2 -0
  33. package/dist/utils/constants.js +5 -0
  34. package/dist/utils/focusManager.d.ts +10 -0
  35. package/dist/utils/focusManager.js +34 -0
  36. package/dist/utils/index.d.ts +3 -0
  37. package/dist/utils/index.js +13 -0
  38. package/dist/utils/keyboardEvents.d.ts +7 -0
  39. package/dist/utils/keyboardEvents.js +88 -0
  40. package/dist/utils/platform.d.ts +11 -0
  41. package/dist/utils/platform.js +19 -0
  42. package/dist/utils/scrollManager.d.ts +21 -0
  43. package/dist/utils/scrollManager.js +37 -0
  44. package/docs/android.md +31 -0
  45. package/docs/api.md +78 -0
  46. package/docs/assets/demo-preview.svg +24 -0
  47. package/docs/installation.md +22 -0
  48. package/docs/ios.md +27 -0
  49. package/docs/troubleshooting.md +44 -0
  50. package/package.json +90 -4
  51. package/src/components/KeyboardAwareScrollView.tsx +240 -0
  52. package/src/components/KeyboardAwareView.tsx +172 -0
  53. package/src/components/KeyboardDismissButton.tsx +32 -0
  54. package/src/components/KeyboardToolbar.tsx +200 -0
  55. package/src/components/index.ts +4 -0
  56. package/src/context/KeyboardContext.tsx +35 -0
  57. package/src/context/index.ts +1 -0
  58. package/src/hooks/index.ts +4 -0
  59. package/src/hooks/useFocusedInput.ts +52 -0
  60. package/src/hooks/useKeyboard.ts +1 -0
  61. package/src/hooks/useKeyboardAnimation.ts +50 -0
  62. package/src/hooks/useKeyboardState.ts +14 -0
  63. package/src/index.ts +35 -0
  64. package/src/types/index.ts +101 -0
  65. package/src/utils/constants.ts +2 -0
  66. package/src/utils/focusManager.ts +40 -0
  67. package/src/utils/index.ts +10 -0
  68. package/src/utils/keyboardEvents.ts +110 -0
  69. package/src/utils/platform.ts +23 -0
  70. package/src/utils/scrollManager.ts +71 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,24 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here.
4
+
5
+ ## 1.0.1 - 2026-11-05
6
+
7
+ - Fix absolutely positioned `KeyboardAwareView` footers remaining behind the
8
+ keyboard; animate them above the keyboard and restore their position on hide.
9
+ - Update the demo with a scrollable form and a bottom-sticky submit action.
10
+ - Add sticky footer regression tests and clarify platform/layout guidance.
11
+ - Rewrite the README with concise installation, form, sticky button, and API
12
+ examples.
13
+
14
+ ## 1.0.0 - 2026-11-04
15
+
16
+ - Added keyboard-aware scrolling, view layout adjustment, toolbar navigation,
17
+ dismissal controls, and shared keyboard hooks for iOS and Android.
18
+ - Added public imperative ref types for both keyboard-aware components.
19
+ - Added platform event, focus, component-ref, and lifecycle tests.
20
+ - Documented installation, APIs, platform behavior, and device testing guidance.
21
+ - Initial release candidate with keyboard-aware views and scrolling.
22
+ - Added shared keyboard state and animation/focus hooks.
23
+ - Added toolbar and keyboard dismissal components.
24
+ - Added local React Native demo, tests, and platform guidance.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Denish Dabhi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,257 @@
1
- # Temporary Holding Version
1
+ # react-native-keyboard-toolkit
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![npm version](https://img.shields.io/npm/v/react-native-keyboard-toolkit.svg)](https://www.npmjs.com/package/react-native-keyboard-toolkit)
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)
6
+
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.
8
+
9
+ ## Features
10
+
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.
18
+
19
+ ## Installation
20
+
21
+ ```sh
22
+ npm install react-native-keyboard-toolkit
23
+ # or
24
+ yarn add react-native-keyboard-toolkit
25
+ ```
26
+
27
+ **Requirements:** React `>=18.2.0`, React Native `>=0.71.0`. No iOS setup is needed.
28
+
29
+ On Android, set `adjustResize` on your activity in `AndroidManifest.xml`:
30
+
31
+ ```xml
32
+ <activity
33
+ android:name=".MainActivity"
34
+ android:windowSoftInputMode="adjustResize"
35
+ ... />
36
+ ```
37
+
38
+ ## Quick start
39
+
40
+ ### Keyboard-aware form
41
+
42
+ ```tsx
43
+ import React from 'react';
44
+ import { TextInput } from 'react-native';
45
+ import { KeyboardAwareScrollView } from 'react-native-keyboard-toolkit';
46
+
47
+ export function ContactForm() {
48
+ return (
49
+ <KeyboardAwareScrollView
50
+ keyboardShouldPersistTaps="handled"
51
+ keyboardSpacing={16}
52
+ contentContainerStyle={{ padding: 20 }}
53
+ >
54
+ <TextInput placeholder="Name" />
55
+ <TextInput placeholder="Email" keyboardType="email-address" />
56
+ <TextInput placeholder="Message" multiline />
57
+ </KeyboardAwareScrollView>
58
+ );
59
+ }
60
+ ```
61
+
62
+ ### Sticky submit button
63
+
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.
65
+
66
+ ```tsx
67
+ import React from 'react';
68
+ import { Pressable, Text, TextInput, View } from 'react-native';
69
+ import {
70
+ KeyboardAwareScrollView,
71
+ KeyboardAwareView,
72
+ } from 'react-native-keyboard-toolkit';
73
+
74
+ export function StickyForm() {
75
+ return (
76
+ <View style={{ flex: 1 }}>
77
+ <KeyboardAwareScrollView
78
+ keyboardShouldPersistTaps="handled"
79
+ keyboardSpacing={112} // leave room for the footer
80
+ contentContainerStyle={{ padding: 20, paddingBottom: 96 }}
81
+ >
82
+ <TextInput placeholder="Name" />
83
+ <TextInput placeholder="Email" keyboardType="email-address" />
84
+ <TextInput placeholder="Company" />
85
+ <TextInput placeholder="Message" multiline />
86
+ </KeyboardAwareScrollView>
87
+
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>
102
+ </KeyboardAwareView>
103
+ </View>
104
+ );
105
+ }
106
+ ```
107
+
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.
113
+
114
+ ```tsx
115
+ import React, { useRef } from 'react';
116
+ import { TextInput, View } from 'react-native';
117
+ import {
118
+ KeyboardAwareScrollView,
119
+ KeyboardToolbar,
120
+ type TextInputRef,
121
+ } from 'react-native-keyboard-toolkit';
122
+
123
+ export function SignUpForm() {
124
+ const name = useRef<TextInputRef>(null);
125
+ const email = useRef<TextInputRef>(null);
126
+ const password = useRef<TextInputRef>(null);
127
+
128
+ return (
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>
135
+
136
+ <KeyboardToolbar inputRefs={[name, email, password]} />
137
+ </View>
138
+ );
139
+ }
140
+ ```
141
+
142
+ ## API
143
+
144
+ ### `KeyboardAwareScrollView`
145
+
146
+ Accepts all `ScrollView` props, forwards its ref to the `ScrollView`, and adds:
147
+
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. |
153
+
154
+ ### `KeyboardAwareView`
155
+
156
+ Accepts all `View` props, forwards its ref to the native view, and supports `behavior`, `keyboardSpacing` (default `16`), and `enabled`.
157
+
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.
161
+
162
+ ### `KeyboardToolbar`
163
+
164
+ Accepts all `View` props plus:
165
+
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. |
174
+
175
+ ### `KeyboardDismissButton`
176
+
177
+ A `Pressable` (accepts `View` props) that dismisses the keyboard when pressed.
178
+
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. |
185
+
186
+ ### `KeyboardProvider`
187
+
188
+ Optional. Wrap your app in `<KeyboardProvider debug>` to log keyboard changes in development builds. Components and hooks work without it.
189
+
190
+ ### Behaviors
191
+
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 |
199
+
200
+ ### Hooks
201
+
202
+ ```tsx
203
+ import {
204
+ useKeyboardState,
205
+ useKeyboardAnimation,
206
+ useFocusedInput,
207
+ } from 'react-native-keyboard-toolkit';
208
+
209
+ const { isVisible, height, progress, animationDuration, screenY } =
210
+ useKeyboardState(); // `useKeyboard()` is an alias
211
+
212
+ const { progress: anim, animationDuration: ms } = useKeyboardAnimation();
213
+ // `anim` is an Animated.Value (0 → 1); updates instantly if Reduce Motion is on
214
+
215
+ const { focusedInput, refresh } = useFocusedInput();
216
+ // focusedInput: { x, y, width, height } in window coordinates, or null
217
+ ```
218
+
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 |
224
+
225
+ Example: animate a view with the keyboard.
226
+
227
+ ```tsx
228
+ import { Animated } from 'react-native';
229
+ import { useKeyboardAnimation } from 'react-native-keyboard-toolkit';
230
+
231
+ function FadeWithKeyboard({ children }: { children: React.ReactNode }) {
232
+ const { progress } = useKeyboardAnimation();
233
+ return (
234
+ <Animated.View style={{ opacity: progress }}>{children}</Animated.View>
235
+ );
236
+ }
237
+ ```
238
+
239
+ ### Utilities and types
240
+
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`.
246
+
247
+ ## Tips
248
+
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.
252
+
253
+ More guides: [Installation](docs/installation.md) · [API](docs/api.md) · [Android](docs/android.md) · [iOS](docs/ios.md) · [Troubleshooting](docs/troubleshooting.md)
254
+
255
+ ## License
256
+
257
+ [MIT](LICENSE) © Denish Dabhi
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ import type { KeyboardAwareScrollViewProps } from '../types';
3
+ import type { KeyboardAwareScrollViewRef } from '../types';
4
+ /** A ScrollView that adds keyboard space and scrolls a covered focused input into view. */
5
+ export declare const KeyboardAwareScrollView: React.ForwardRefExoticComponent<KeyboardAwareScrollViewProps & React.RefAttributes<KeyboardAwareScrollViewRef>>;
6
+ export default KeyboardAwareScrollView;
@@ -0,0 +1,137 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.KeyboardAwareScrollView = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("react");
6
+ const react_native_1 = require("react-native");
7
+ const focusManager_1 = require("../utils/focusManager");
8
+ const scrollManager_1 = require("../utils/scrollManager");
9
+ const KeyboardContext_1 = require("../context/KeyboardContext");
10
+ const useKeyboardState_1 = require("../hooks/useKeyboardState");
11
+ const constants_1 = require("../utils/constants");
12
+ const platform_1 = require("../utils/platform");
13
+ /** A ScrollView that adds keyboard space and scrolls a covered focused input into view. */
14
+ exports.KeyboardAwareScrollView = (0, react_1.forwardRef)(function KeyboardAwareScrollViewComponent({ behavior = 'smart', keyboardSpacing = constants_1.DEFAULT_KEYBOARD_SPACING, enabled = true, contentContainerStyle, onScroll, onScrollBeginDrag, onContentSizeChange, onLayout, onFocusCapture, scrollEventThrottle = 16, ...props }, forwardedRef) {
15
+ const keyboard = (0, useKeyboardState_1.useKeyboardState)();
16
+ const debug = (0, KeyboardContext_1.useKeyboardDebug)();
17
+ const scrollRef = (0, react_1.useRef)(null);
18
+ (0, react_1.useImperativeHandle)(forwardedRef, () => scrollRef.current, []);
19
+ const scrollOffset = (0, react_1.useRef)(0);
20
+ const lastScrollTarget = (0, react_1.useRef)(null);
21
+ const measurementSequence = (0, react_1.useRef)(0);
22
+ const [keyboardOverlap, setKeyboardOverlap] = (0, react_1.useState)(0);
23
+ const spacing = Math.max(0, keyboardSpacing);
24
+ const scrollFocusedInput = (0, react_1.useCallback)(async () => {
25
+ const sequence = ++measurementSequence.current;
26
+ if (!enabled || !keyboard.isVisible || behavior === 'resize') {
27
+ return;
28
+ }
29
+ const scrollView = scrollRef.current;
30
+ const input = (0, focusManager_1.getFocusedInput)();
31
+ if (!scrollView?.measureInWindow) {
32
+ return;
33
+ }
34
+ const viewport = await new Promise(resolve => {
35
+ scrollView.measureInWindow((_x, y, _width, height) => resolve({ y, height }));
36
+ });
37
+ if (sequence !== measurementSequence.current || !keyboard.isVisible) {
38
+ return;
39
+ }
40
+ const overlap = (0, scrollManager_1.calculateKeyboardOverlap)(viewport, keyboard.height, keyboard.screenY);
41
+ if (Math.abs(overlap - keyboardOverlap) > 1) {
42
+ setKeyboardOverlap(overlap);
43
+ return;
44
+ }
45
+ if (!input?.measureInWindow ||
46
+ behavior === 'none' ||
47
+ behavior === 'padding') {
48
+ return;
49
+ }
50
+ const inputLayout = await (0, focusManager_1.measureFocusedInput)(input);
51
+ if (sequence !== measurementSequence.current || !inputLayout) {
52
+ return;
53
+ }
54
+ const calculation = (0, scrollManager_1.calculateScrollOffset)({ y: inputLayout.y, height: inputLayout.height }, viewport, keyboard.height, spacing, scrollOffset.current, keyboard.screenY);
55
+ if (debug && __DEV__) {
56
+ console.debug('[keyboard-toolkit] scroll calculation', calculation);
57
+ }
58
+ if (Math.abs(calculation.delta) > 1 &&
59
+ (lastScrollTarget.current === null ||
60
+ Math.abs(lastScrollTarget.current - calculation.offset) > 1)) {
61
+ lastScrollTarget.current = calculation.offset;
62
+ scrollView.scrollTo({ y: calculation.offset, animated: true });
63
+ }
64
+ }, [
65
+ behavior,
66
+ debug,
67
+ enabled,
68
+ keyboard.height,
69
+ keyboard.isVisible,
70
+ keyboard.screenY,
71
+ keyboardOverlap,
72
+ spacing,
73
+ ]);
74
+ (0, react_1.useEffect)(() => {
75
+ if (!keyboard.isVisible) {
76
+ lastScrollTarget.current = null;
77
+ measurementSequence.current += 1;
78
+ setKeyboardOverlap(0);
79
+ return;
80
+ }
81
+ const frame = requestAnimationFrame(() => {
82
+ scrollFocusedInput();
83
+ });
84
+ return () => cancelAnimationFrame(frame);
85
+ }, [keyboard.height, keyboard.isVisible, scrollFocusedInput]);
86
+ const handleScroll = (0, react_1.useCallback)((event) => {
87
+ scrollOffset.current = event.nativeEvent.contentOffset.y;
88
+ if (lastScrollTarget.current !== null &&
89
+ Math.abs(scrollOffset.current - lastScrollTarget.current) <= 1) {
90
+ lastScrollTarget.current = null;
91
+ }
92
+ onScroll?.(event);
93
+ }, [onScroll]);
94
+ const handleScrollBeginDrag = (0, react_1.useCallback)((event) => {
95
+ lastScrollTarget.current = null;
96
+ onScrollBeginDrag?.(event);
97
+ }, [onScrollBeginDrag]);
98
+ const handleContentSizeChange = (0, react_1.useCallback)((width, height) => {
99
+ onContentSizeChange?.(width, height);
100
+ if (keyboard.isVisible) {
101
+ requestAnimationFrame(() => scrollFocusedInput());
102
+ }
103
+ }, [keyboard.isVisible, onContentSizeChange, scrollFocusedInput]);
104
+ const handleLayout = (0, react_1.useCallback)((event) => {
105
+ onLayout?.(event);
106
+ if (keyboard.isVisible) {
107
+ requestAnimationFrame(() => scrollFocusedInput());
108
+ }
109
+ }, [keyboard.isVisible, onLayout, scrollFocusedInput]);
110
+ const handleFocusCapture = (0, react_1.useCallback)((event) => {
111
+ onFocusCapture?.(event);
112
+ if (keyboard.isVisible) {
113
+ requestAnimationFrame(() => scrollFocusedInput());
114
+ }
115
+ }, [keyboard.isVisible, onFocusCapture, scrollFocusedInput]);
116
+ const flattenedContentStyle = react_native_1.StyleSheet.flatten(contentContainerStyle);
117
+ const existingBottomPadding = typeof flattenedContentStyle?.paddingBottom === 'number'
118
+ ? flattenedContentStyle.paddingBottom
119
+ : 0;
120
+ const shouldPad = enabled &&
121
+ keyboard.isVisible &&
122
+ behavior !== 'none' &&
123
+ behavior !== 'resize' &&
124
+ keyboardOverlap > 0;
125
+ const adjustedContentStyle = shouldPad && typeof flattenedContentStyle?.paddingBottom !== 'string'
126
+ ? [
127
+ contentContainerStyle,
128
+ {
129
+ paddingBottom: Math.max(existingBottomPadding, keyboardOverlap + spacing),
130
+ },
131
+ ]
132
+ : contentContainerStyle;
133
+ return ((0, jsx_runtime_1.jsx)(react_native_1.ScrollView, { ...props, automaticallyAdjustKeyboardInsets: props.automaticallyAdjustKeyboardInsets ??
134
+ (behavior === 'resize' && platform_1.isIOS), ref: scrollRef, contentContainerStyle: adjustedContentStyle, onContentSizeChange: handleContentSizeChange, onLayout: handleLayout, onFocusCapture: handleFocusCapture, onScroll: handleScroll, onScrollBeginDrag: handleScrollBeginDrag, scrollEventThrottle: scrollEventThrottle }));
135
+ });
136
+ exports.KeyboardAwareScrollView.displayName = 'KeyboardAwareScrollView';
137
+ exports.default = exports.KeyboardAwareScrollView;
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ import type { KeyboardAwareViewProps } from '../types';
3
+ import type { KeyboardAwareViewRef } from '../types';
4
+ /** Adds keyboard spacing to in-flow content and keeps absolute footers visible. */
5
+ export declare const KeyboardAwareView: React.ForwardRefExoticComponent<KeyboardAwareViewProps & React.RefAttributes<KeyboardAwareViewRef>>;
6
+ export default KeyboardAwareView;
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.KeyboardAwareView = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("react");
6
+ const react_native_1 = require("react-native");
7
+ const constants_1 = require("../utils/constants");
8
+ const useKeyboardState_1 = require("../hooks/useKeyboardState");
9
+ const scrollManager_1 = require("../utils/scrollManager");
10
+ /** Adds keyboard spacing to in-flow content and keeps absolute footers visible. */
11
+ exports.KeyboardAwareView = (0, react_1.forwardRef)(function KeyboardAwareViewComponent({ behavior = 'smart', keyboardSpacing = constants_1.DEFAULT_KEYBOARD_SPACING, enabled = true, style, onLayout, ...props }, forwardedRef) {
12
+ const keyboard = (0, useKeyboardState_1.useKeyboardState)();
13
+ const viewRef = (0, react_1.useRef)(null);
14
+ (0, react_1.useImperativeHandle)(forwardedRef, () => viewRef.current, []);
15
+ const measurementSequence = (0, react_1.useRef)(0);
16
+ const [keyboardOverlap, setKeyboardOverlap] = (0, react_1.useState)(0);
17
+ const [keyboardDisplacement, setKeyboardDisplacement] = (0, react_1.useState)(0);
18
+ const animatedDisplacement = (0, react_1.useRef)(new react_native_1.Animated.Value(0)).current;
19
+ const flattenedStyle = react_native_1.StyleSheet.flatten(style);
20
+ const isAbsolutelyPositioned = flattenedStyle?.position === 'absolute';
21
+ const spacing = Number.isFinite(keyboardSpacing)
22
+ ? Math.max(0, keyboardSpacing)
23
+ : 0;
24
+ const basePadding = typeof flattenedStyle?.paddingBottom === 'number'
25
+ ? flattenedStyle.paddingBottom
26
+ : 0;
27
+ const measureOverlap = (0, react_1.useCallback)(() => {
28
+ const view = viewRef.current;
29
+ if (!view?.measureInWindow || !keyboard.isVisible) {
30
+ return;
31
+ }
32
+ const sequence = ++measurementSequence.current;
33
+ view.measureInWindow((_x, y, _width, height) => {
34
+ if (sequence !== measurementSequence.current || !keyboard.isVisible) {
35
+ return;
36
+ }
37
+ if (!isAbsolutelyPositioned) {
38
+ setKeyboardOverlap((0, scrollManager_1.calculateKeyboardOverlap)({ y, height }, keyboard.height, keyboard.screenY));
39
+ }
40
+ setKeyboardDisplacement(isAbsolutelyPositioned && typeof keyboard.screenY === 'number'
41
+ ? (0, scrollManager_1.calculateKeyboardDisplacement)({ y, height }, keyboard.screenY, spacing)
42
+ : 0);
43
+ });
44
+ }, [
45
+ isAbsolutelyPositioned,
46
+ keyboard.height,
47
+ keyboard.isVisible,
48
+ keyboard.screenY,
49
+ spacing,
50
+ ]);
51
+ (0, react_1.useEffect)(() => {
52
+ if (!keyboard.isVisible) {
53
+ measurementSequence.current += 1;
54
+ setKeyboardOverlap(0);
55
+ setKeyboardDisplacement(0);
56
+ return;
57
+ }
58
+ const frame = requestAnimationFrame(measureOverlap);
59
+ return () => cancelAnimationFrame(frame);
60
+ }, [keyboard.isVisible, keyboard.height, keyboard.screenY, measureOverlap]);
61
+ const shouldAdjust = enabled &&
62
+ keyboard.isVisible &&
63
+ behavior !== 'none' &&
64
+ behavior !== 'resize';
65
+ (0, react_1.useEffect)(() => {
66
+ if (!isAbsolutelyPositioned) {
67
+ animatedDisplacement.setValue(0);
68
+ return;
69
+ }
70
+ const animation = react_native_1.Animated.timing(animatedDisplacement, {
71
+ toValue: shouldAdjust ? -keyboardDisplacement : 0,
72
+ duration: keyboard.animationDuration,
73
+ useNativeDriver: true,
74
+ });
75
+ animation.start();
76
+ return () => animation.stop();
77
+ }, [
78
+ animatedDisplacement,
79
+ isAbsolutelyPositioned,
80
+ keyboard.animationDuration,
81
+ keyboardDisplacement,
82
+ shouldAdjust,
83
+ ]);
84
+ const handleLayout = (0, react_1.useCallback)((event) => {
85
+ onLayout?.(event);
86
+ measureOverlap();
87
+ }, [measureOverlap, onLayout]);
88
+ const adjustPadding = shouldAdjust &&
89
+ !isAbsolutelyPositioned &&
90
+ keyboardOverlap > 0 &&
91
+ typeof flattenedStyle?.paddingBottom !== 'string';
92
+ const stickyTransform = isAbsolutelyPositioned
93
+ ? [
94
+ ...(Array.isArray(flattenedStyle?.transform)
95
+ ? flattenedStyle.transform
96
+ : []),
97
+ { translateY: animatedDisplacement },
98
+ ]
99
+ : undefined;
100
+ return ((0, jsx_runtime_1.jsx)(react_native_1.Animated.View, { ...props, ref: viewRef, onLayout: handleLayout, style: adjustPadding
101
+ ? [
102
+ style,
103
+ {
104
+ paddingBottom: Math.max(basePadding, keyboardOverlap + spacing),
105
+ },
106
+ ]
107
+ : isAbsolutelyPositioned
108
+ ? [style, { transform: stickyTransform }]
109
+ : style }));
110
+ });
111
+ exports.KeyboardAwareView.displayName = 'KeyboardAwareView';
112
+ exports.default = exports.KeyboardAwareView;
@@ -0,0 +1,5 @@
1
+ import React from 'react';
2
+ import type { KeyboardDismissButtonProps } from '../types';
3
+ /** A pressable that dismisses the software keyboard by default. */
4
+ export declare function KeyboardDismissButton({ children, onPress, dismissKeyboard, accessibilityLabel, ...props }: KeyboardDismissButtonProps): React.JSX.Element;
5
+ export default KeyboardDismissButton;
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.KeyboardDismissButton = KeyboardDismissButton;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_native_1 = require("react-native");
6
+ /** A pressable that dismisses the software keyboard by default. */
7
+ function KeyboardDismissButton({ children, onPress, dismissKeyboard = true, accessibilityLabel = 'Dismiss keyboard', ...props }) {
8
+ const handlePress = () => {
9
+ onPress?.();
10
+ if (dismissKeyboard) {
11
+ react_native_1.Keyboard.dismiss();
12
+ }
13
+ };
14
+ return ((0, jsx_runtime_1.jsx)(react_native_1.Pressable, { ...props, accessibilityLabel: accessibilityLabel, accessibilityRole: "button", onPress: handlePress, children: children }));
15
+ }
16
+ exports.default = KeyboardDismissButton;
@@ -0,0 +1,5 @@
1
+ import React from 'react';
2
+ import type { KeyboardToolbarProps } from '../types';
3
+ /** An optional in-layout toolbar for input navigation and keyboard dismissal. */
4
+ export declare function KeyboardToolbar({ inputRefs, focusedInputIndex, onFocusedInputChange, onDone, doneText, children, alwaysVisible, style, ...props }: KeyboardToolbarProps): React.JSX.Element | null;
5
+ export default KeyboardToolbar;