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
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.KeyboardToolbar = KeyboardToolbar;
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 useKeyboardState_1 = require("../hooks/useKeyboardState");
8
+ function findFocusedInputIndex(inputRefs) {
9
+ return inputRefs.findIndex(ref => ref.current?.isFocused() === true);
10
+ }
11
+ function isMountedInputAt(inputRefs, index) {
12
+ return (index >= 0 && index < inputRefs.length && inputRefs[index]?.current != null);
13
+ }
14
+ function hasMountedInputBefore(inputRefs, index) {
15
+ return inputRefs.some((input, inputIndex) => inputIndex < index && input.current != null);
16
+ }
17
+ function hasMountedInputAfter(inputRefs, index) {
18
+ return inputRefs.some((input, inputIndex) => inputIndex > index && input.current != null);
19
+ }
20
+ /** An optional in-layout toolbar for input navigation and keyboard dismissal. */
21
+ function KeyboardToolbar({ inputRefs = [], focusedInputIndex, onFocusedInputChange, onDone, doneText = 'Done', children, alwaysVisible = false, style, ...props }) {
22
+ const keyboard = (0, useKeyboardState_1.useKeyboardState)();
23
+ const [trackedIndex, setTrackedIndex] = (0, react_1.useState)(-1);
24
+ const lastReportedIndex = (0, react_1.useRef)(-1);
25
+ const trackFocus = (0, react_1.useCallback)(() => {
26
+ const index = findFocusedInputIndex(inputRefs);
27
+ setTrackedIndex(current => (current === index ? current : index));
28
+ if (lastReportedIndex.current !== index) {
29
+ lastReportedIndex.current = index;
30
+ onFocusedInputChange?.(index);
31
+ }
32
+ }, [inputRefs, onFocusedInputChange]);
33
+ (0, react_1.useEffect)(() => {
34
+ trackFocus();
35
+ if (!keyboard.isVisible && !alwaysVisible) {
36
+ return;
37
+ }
38
+ const interval = setInterval(trackFocus, 100);
39
+ return () => clearInterval(interval);
40
+ }, [alwaysVisible, keyboard.isVisible, trackFocus]);
41
+ if (!alwaysVisible && !keyboard.isVisible) {
42
+ return null;
43
+ }
44
+ const actualFocusedIndex = findFocusedInputIndex(inputRefs);
45
+ const currentIndex = actualFocusedIndex >= 0
46
+ ? actualFocusedIndex
47
+ : isMountedInputAt(inputRefs, trackedIndex)
48
+ ? trackedIndex
49
+ : isMountedInputAt(inputRefs, focusedInputIndex ?? -1)
50
+ ? focusedInputIndex
51
+ : -1;
52
+ const canNavigateBackward = currentIndex >= 0 && hasMountedInputBefore(inputRefs, currentIndex);
53
+ const canNavigateForward = currentIndex >= 0 && hasMountedInputAfter(inputRefs, currentIndex);
54
+ const focusInDirection = (direction) => {
55
+ const actualIndex = findFocusedInputIndex(inputRefs);
56
+ const origin = actualIndex >= 0 ? actualIndex : currentIndex;
57
+ if (origin < 0) {
58
+ return;
59
+ }
60
+ for (let index = origin + direction; index >= 0 && index < inputRefs.length; index += direction) {
61
+ const target = inputRefs[index]?.current;
62
+ if (target) {
63
+ target.focus();
64
+ lastReportedIndex.current = index;
65
+ setTrackedIndex(index);
66
+ onFocusedInputChange?.(index);
67
+ return;
68
+ }
69
+ }
70
+ };
71
+ const done = () => {
72
+ const focusedInput = react_native_1.TextInput.State.currentlyFocusedInput?.();
73
+ if (focusedInput) {
74
+ react_native_1.TextInput.State.blurTextInput(focusedInput);
75
+ }
76
+ react_native_1.Keyboard.dismiss();
77
+ onDone?.();
78
+ };
79
+ return ((0, jsx_runtime_1.jsx)(react_native_1.View, { ...props, style: [styles.toolbar, style], children: children ?? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(react_native_1.Pressable, { accessibilityRole: "button", accessibilityLabel: "Previous input", disabled: !canNavigateBackward, onPress: () => focusInDirection(-1), style: styles.action, children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: canNavigateBackward ? styles.label : styles.disabled, children: "Previous" }) }), (0, jsx_runtime_1.jsx)(react_native_1.Pressable, { accessibilityRole: "button", accessibilityLabel: "Next input", disabled: !canNavigateForward, onPress: () => focusInDirection(1), style: styles.action, children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: canNavigateForward ? styles.label : styles.disabled, children: "Next" }) }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.spacer }), (0, jsx_runtime_1.jsx)(react_native_1.Pressable, { accessibilityRole: "button", accessibilityLabel: "Done", onPress: done, style: styles.action, children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.label, children: doneText }) })] })) }));
80
+ }
81
+ const styles = react_native_1.StyleSheet.create({
82
+ toolbar: {
83
+ alignItems: 'center',
84
+ backgroundColor: '#f6f7f9',
85
+ borderTopColor: '#d8dce2',
86
+ borderTopWidth: react_native_1.StyleSheet.hairlineWidth,
87
+ flexDirection: 'row',
88
+ minHeight: 48,
89
+ paddingHorizontal: 12,
90
+ },
91
+ action: {
92
+ minHeight: 44,
93
+ justifyContent: 'center',
94
+ paddingHorizontal: 10,
95
+ },
96
+ label: {
97
+ color: '#155eef',
98
+ fontSize: 15,
99
+ fontWeight: '600',
100
+ },
101
+ disabled: {
102
+ color: '#98a2b3',
103
+ fontSize: 15,
104
+ },
105
+ spacer: {
106
+ flex: 1,
107
+ },
108
+ });
109
+ exports.default = KeyboardToolbar;
@@ -0,0 +1,4 @@
1
+ export { KeyboardAwareScrollView } from './KeyboardAwareScrollView';
2
+ export { KeyboardAwareView } from './KeyboardAwareView';
3
+ export { KeyboardToolbar } from './KeyboardToolbar';
4
+ export { KeyboardDismissButton } from './KeyboardDismissButton';
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.KeyboardDismissButton = exports.KeyboardToolbar = exports.KeyboardAwareView = exports.KeyboardAwareScrollView = void 0;
4
+ var KeyboardAwareScrollView_1 = require("./KeyboardAwareScrollView");
5
+ Object.defineProperty(exports, "KeyboardAwareScrollView", { enumerable: true, get: function () { return KeyboardAwareScrollView_1.KeyboardAwareScrollView; } });
6
+ var KeyboardAwareView_1 = require("./KeyboardAwareView");
7
+ Object.defineProperty(exports, "KeyboardAwareView", { enumerable: true, get: function () { return KeyboardAwareView_1.KeyboardAwareView; } });
8
+ var KeyboardToolbar_1 = require("./KeyboardToolbar");
9
+ Object.defineProperty(exports, "KeyboardToolbar", { enumerable: true, get: function () { return KeyboardToolbar_1.KeyboardToolbar; } });
10
+ var KeyboardDismissButton_1 = require("./KeyboardDismissButton");
11
+ Object.defineProperty(exports, "KeyboardDismissButton", { enumerable: true, get: function () { return KeyboardDismissButton_1.KeyboardDismissButton; } });
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ import type { KeyboardProviderProps } from '../types';
3
+ /** Optional provider for development diagnostics; keyboard subscriptions are shared globally. */
4
+ export declare function KeyboardProvider({ children, debug, }: KeyboardProviderProps): React.JSX.Element;
5
+ /** Read whether development diagnostics are enabled on the nearest provider. */
6
+ export declare function useKeyboardDebug(): boolean;
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.KeyboardProvider = KeyboardProvider;
4
+ exports.useKeyboardDebug = useKeyboardDebug;
5
+ const jsx_runtime_1 = require("react/jsx-runtime");
6
+ const react_1 = require("react");
7
+ const useKeyboardState_1 = require("../hooks/useKeyboardState");
8
+ const DebugContext = (0, react_1.createContext)(false);
9
+ /** Optional provider for development diagnostics; keyboard subscriptions are shared globally. */
10
+ function KeyboardProvider({ children, debug = false, }) {
11
+ const keyboard = (0, useKeyboardState_1.useKeyboardState)();
12
+ const previous = (0, react_1.useRef)(keyboard);
13
+ (0, react_1.useEffect)(() => {
14
+ if (debug &&
15
+ __DEV__ &&
16
+ (previous.current.isVisible !== keyboard.isVisible ||
17
+ previous.current.height !== keyboard.height)) {
18
+ console.debug('[keyboard-toolkit]', keyboard);
19
+ }
20
+ previous.current = keyboard;
21
+ }, [debug, keyboard]);
22
+ return ((0, jsx_runtime_1.jsx)(DebugContext.Provider, { value: debug, children: children }));
23
+ }
24
+ /** Read whether development diagnostics are enabled on the nearest provider. */
25
+ function useKeyboardDebug() {
26
+ return (0, react_1.useContext)(DebugContext);
27
+ }
@@ -0,0 +1 @@
1
+ export { KeyboardProvider } from './KeyboardContext';
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.KeyboardProvider = void 0;
4
+ var KeyboardContext_1 = require("./KeyboardContext");
5
+ Object.defineProperty(exports, "KeyboardProvider", { enumerable: true, get: function () { return KeyboardContext_1.KeyboardProvider; } });
@@ -0,0 +1,4 @@
1
+ export { useKeyboard } from './useKeyboard';
2
+ export { useKeyboardState } from './useKeyboardState';
3
+ export { useKeyboardAnimation } from './useKeyboardAnimation';
4
+ export { useFocusedInput } from './useFocusedInput';
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useFocusedInput = exports.useKeyboardAnimation = exports.useKeyboardState = exports.useKeyboard = void 0;
4
+ var useKeyboard_1 = require("./useKeyboard");
5
+ Object.defineProperty(exports, "useKeyboard", { enumerable: true, get: function () { return useKeyboard_1.useKeyboard; } });
6
+ var useKeyboardState_1 = require("./useKeyboardState");
7
+ Object.defineProperty(exports, "useKeyboardState", { enumerable: true, get: function () { return useKeyboardState_1.useKeyboardState; } });
8
+ var useKeyboardAnimation_1 = require("./useKeyboardAnimation");
9
+ Object.defineProperty(exports, "useKeyboardAnimation", { enumerable: true, get: function () { return useKeyboardAnimation_1.useKeyboardAnimation; } });
10
+ var useFocusedInput_1 = require("./useFocusedInput");
11
+ Object.defineProperty(exports, "useFocusedInput", { enumerable: true, get: function () { return useFocusedInput_1.useFocusedInput; } });
@@ -0,0 +1,6 @@
1
+ import type { FocusedInputLayout } from '../types';
2
+ /** Measure the focused TextInput when the keyboard opens; call `refresh` after layout changes. */
3
+ export declare function useFocusedInput(): {
4
+ focusedInput: FocusedInputLayout | null;
5
+ refresh: () => Promise<FocusedInputLayout | null>;
6
+ };
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useFocusedInput = useFocusedInput;
4
+ const react_1 = require("react");
5
+ const focusManager_1 = require("../utils/focusManager");
6
+ const useKeyboardState_1 = require("./useKeyboardState");
7
+ /** Measure the focused TextInput when the keyboard opens; call `refresh` after layout changes. */
8
+ function useFocusedInput() {
9
+ const keyboard = (0, useKeyboardState_1.useKeyboardState)();
10
+ const [focusedInput, setFocusedInput] = (0, react_1.useState)(null);
11
+ const measureSequence = (0, react_1.useRef)(0);
12
+ const refresh = (0, react_1.useCallback)(async () => {
13
+ const sequence = ++measureSequence.current;
14
+ const layout = await (0, focusManager_1.measureFocusedInput)((0, focusManager_1.getFocusedInput)());
15
+ if (sequence === measureSequence.current) {
16
+ setFocusedInput(current => {
17
+ if (current?.x === layout?.x &&
18
+ current?.y === layout?.y &&
19
+ current?.width === layout?.width &&
20
+ current?.height === layout?.height) {
21
+ return current;
22
+ }
23
+ return layout;
24
+ });
25
+ }
26
+ return layout;
27
+ }, []);
28
+ (0, react_1.useEffect)(() => {
29
+ if (!keyboard.isVisible) {
30
+ measureSequence.current += 1;
31
+ setFocusedInput(null);
32
+ return;
33
+ }
34
+ refresh();
35
+ const interval = setInterval(refresh, 120);
36
+ return () => {
37
+ measureSequence.current += 1;
38
+ clearInterval(interval);
39
+ };
40
+ }, [keyboard.isVisible, keyboard.height, refresh]);
41
+ return { focusedInput, refresh };
42
+ }
@@ -0,0 +1 @@
1
+ export { useKeyboardState as useKeyboard } from './useKeyboardState';
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useKeyboard = void 0;
4
+ var useKeyboardState_1 = require("./useKeyboardState");
5
+ Object.defineProperty(exports, "useKeyboard", { enumerable: true, get: function () { return useKeyboardState_1.useKeyboardState; } });
@@ -0,0 +1,6 @@
1
+ import { Animated } from 'react-native';
2
+ /** Provides an animated 0-to-1 visibility value and the native event duration. */
3
+ export declare function useKeyboardAnimation(): {
4
+ progress: Animated.Value;
5
+ animationDuration: number;
6
+ };
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useKeyboardAnimation = useKeyboardAnimation;
4
+ const react_1 = require("react");
5
+ const react_native_1 = require("react-native");
6
+ const useKeyboardState_1 = require("./useKeyboardState");
7
+ /** Provides an animated 0-to-1 visibility value and the native event duration. */
8
+ function useKeyboardAnimation() {
9
+ const keyboard = (0, useKeyboardState_1.useKeyboardState)();
10
+ const progressRef = (0, react_1.useRef)(null);
11
+ if (!progressRef.current) {
12
+ progressRef.current = new react_native_1.Animated.Value(keyboard.progress);
13
+ }
14
+ const progress = progressRef.current;
15
+ const [reduceMotion, setReduceMotion] = (0, react_1.useState)(false);
16
+ (0, react_1.useEffect)(() => {
17
+ let active = true;
18
+ react_native_1.AccessibilityInfo.isReduceMotionEnabled()
19
+ .then(enabled => {
20
+ if (active) {
21
+ setReduceMotion(enabled);
22
+ }
23
+ })
24
+ .catch(() => {
25
+ // Keep animations enabled when the platform preference cannot be read.
26
+ });
27
+ const subscription = react_native_1.AccessibilityInfo.addEventListener('reduceMotionChanged', setReduceMotion);
28
+ return () => {
29
+ active = false;
30
+ subscription.remove();
31
+ };
32
+ }, []);
33
+ (0, react_1.useEffect)(() => {
34
+ const animation = react_native_1.Animated.timing(progress, {
35
+ toValue: keyboard.progress,
36
+ duration: reduceMotion ? 0 : keyboard.animationDuration,
37
+ useNativeDriver: true,
38
+ });
39
+ animation.start();
40
+ return () => animation.stop();
41
+ }, [keyboard.animationDuration, keyboard.progress, progress, reduceMotion]);
42
+ return { progress, animationDuration: keyboard.animationDuration };
43
+ }
@@ -0,0 +1,2 @@
1
+ /** Subscribe to the shared, normalized keyboard state. A provider is optional. */
2
+ export declare function useKeyboardState(): import("..").KeyboardState;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useKeyboardState = useKeyboardState;
4
+ const react_1 = require("react");
5
+ const keyboardEvents_1 = require("../utils/keyboardEvents");
6
+ /** Subscribe to the shared, normalized keyboard state. A provider is optional. */
7
+ function useKeyboardState() {
8
+ return (0, react_1.useSyncExternalStore)(keyboardEvents_1.subscribeKeyboardState, keyboardEvents_1.getKeyboardState, keyboardEvents_1.getKeyboardState);
9
+ }
@@ -0,0 +1,5 @@
1
+ export { KeyboardAwareScrollView, KeyboardAwareView, KeyboardDismissButton, KeyboardToolbar, } from './components';
2
+ export { KeyboardProvider } from './context';
3
+ export { useFocusedInput, useKeyboard, useKeyboardAnimation, useKeyboardState, } from './hooks';
4
+ export { calculateScrollOffset, calculateKeyboardOverlap, focusInput, getFocusedInput, isAndroid, isIOS, measureFocusedInput, } from './utils';
5
+ export type { FocusedInputLayout, KeyboardAwareScrollViewProps, KeyboardAwareScrollViewRef, KeyboardAwareViewProps, KeyboardAwareViewRef, KeyboardBehavior, KeyboardDismissButtonProps, KeyboardProviderProps, KeyboardState, KeyboardToolbarProps, TextInputRef, } from './types';
package/dist/index.js ADDED
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.measureFocusedInput = exports.isIOS = exports.isAndroid = exports.getFocusedInput = exports.focusInput = exports.calculateKeyboardOverlap = exports.calculateScrollOffset = exports.useKeyboardState = exports.useKeyboardAnimation = exports.useKeyboard = exports.useFocusedInput = exports.KeyboardProvider = exports.KeyboardToolbar = exports.KeyboardDismissButton = exports.KeyboardAwareView = exports.KeyboardAwareScrollView = void 0;
4
+ var components_1 = require("./components");
5
+ Object.defineProperty(exports, "KeyboardAwareScrollView", { enumerable: true, get: function () { return components_1.KeyboardAwareScrollView; } });
6
+ Object.defineProperty(exports, "KeyboardAwareView", { enumerable: true, get: function () { return components_1.KeyboardAwareView; } });
7
+ Object.defineProperty(exports, "KeyboardDismissButton", { enumerable: true, get: function () { return components_1.KeyboardDismissButton; } });
8
+ Object.defineProperty(exports, "KeyboardToolbar", { enumerable: true, get: function () { return components_1.KeyboardToolbar; } });
9
+ var context_1 = require("./context");
10
+ Object.defineProperty(exports, "KeyboardProvider", { enumerable: true, get: function () { return context_1.KeyboardProvider; } });
11
+ var hooks_1 = require("./hooks");
12
+ Object.defineProperty(exports, "useFocusedInput", { enumerable: true, get: function () { return hooks_1.useFocusedInput; } });
13
+ Object.defineProperty(exports, "useKeyboard", { enumerable: true, get: function () { return hooks_1.useKeyboard; } });
14
+ Object.defineProperty(exports, "useKeyboardAnimation", { enumerable: true, get: function () { return hooks_1.useKeyboardAnimation; } });
15
+ Object.defineProperty(exports, "useKeyboardState", { enumerable: true, get: function () { return hooks_1.useKeyboardState; } });
16
+ var utils_1 = require("./utils");
17
+ Object.defineProperty(exports, "calculateScrollOffset", { enumerable: true, get: function () { return utils_1.calculateScrollOffset; } });
18
+ Object.defineProperty(exports, "calculateKeyboardOverlap", { enumerable: true, get: function () { return utils_1.calculateKeyboardOverlap; } });
19
+ Object.defineProperty(exports, "focusInput", { enumerable: true, get: function () { return utils_1.focusInput; } });
20
+ Object.defineProperty(exports, "getFocusedInput", { enumerable: true, get: function () { return utils_1.getFocusedInput; } });
21
+ Object.defineProperty(exports, "isAndroid", { enumerable: true, get: function () { return utils_1.isAndroid; } });
22
+ Object.defineProperty(exports, "isIOS", { enumerable: true, get: function () { return utils_1.isIOS; } });
23
+ Object.defineProperty(exports, "measureFocusedInput", { enumerable: true, get: function () { return utils_1.measureFocusedInput; } });
@@ -0,0 +1,69 @@
1
+ import type { ScrollView, ScrollViewProps, StyleProp, TextInput, View, ViewProps, ViewStyle } from 'react-native';
2
+ import type { ComponentPropsWithRef, ElementRef, ReactNode, RefObject } from 'react';
3
+ type RefTarget<T> = T extends RefObject<infer Value> ? NonNullable<Value> : never;
4
+ export type TextInputRef = RefTarget<NonNullable<ComponentPropsWithRef<typeof TextInput>['ref']>>;
5
+ export type KeyboardAwareScrollViewRef = ElementRef<typeof ScrollView>;
6
+ export type KeyboardAwareViewRef = ElementRef<typeof View>;
7
+ /** Layout strategies supported by the keyboard-aware components. */
8
+ export type KeyboardBehavior = 'smart' | 'scroll' | 'resize' | 'padding' | 'none';
9
+ /** A normalized snapshot of the most recent on-screen keyboard event. */
10
+ export interface KeyboardState {
11
+ isVisible: boolean;
12
+ height: number;
13
+ progress: 0 | 1;
14
+ animationDuration: number;
15
+ screenY?: number;
16
+ }
17
+ export interface KeyboardAwareScrollViewProps extends ScrollViewProps {
18
+ /** Defaults to `smart`, which scrolls only when the focused field is covered. */
19
+ behavior?: KeyboardBehavior;
20
+ /** Space to leave between the focused field and keyboard, in points. */
21
+ keyboardSpacing?: number;
22
+ /** Disable keyboard-driven padding and focus scrolling. */
23
+ enabled?: boolean;
24
+ }
25
+ export interface KeyboardAwareViewProps extends ViewProps {
26
+ /** Defaults to `smart`; absolute-positioned views are moved above the keyboard. */
27
+ behavior?: KeyboardBehavior;
28
+ /** Extra space between the view content and the keyboard, in points. */
29
+ keyboardSpacing?: number;
30
+ /** Disable keyboard-driven layout changes. */
31
+ enabled?: boolean;
32
+ }
33
+ export interface KeyboardProviderProps {
34
+ children: ReactNode;
35
+ /** Log keyboard state changes in development builds. */
36
+ debug?: boolean;
37
+ }
38
+ export interface KeyboardToolbarProps extends ViewProps {
39
+ /** Refs to the form inputs the toolbar should navigate between. */
40
+ inputRefs?: readonly RefObject<TextInputRef | null>[];
41
+ /** @deprecated Focus is detected automatically from the provided input refs. */
42
+ focusedInputIndex?: number;
43
+ /** Called when the focused input in `inputRefs` changes. */
44
+ onFocusedInputChange?: (index: number) => void;
45
+ /** Called after the Done action dismisses the keyboard. */
46
+ onDone?: () => void;
47
+ /** Label shown by the built-in Done button. */
48
+ doneText?: string;
49
+ /** Replaces the built-in previous/next/done controls. */
50
+ children?: ReactNode;
51
+ /** Render the toolbar while the keyboard is hidden. Defaults to false. */
52
+ alwaysVisible?: boolean;
53
+ }
54
+ export interface KeyboardDismissButtonProps extends ViewProps {
55
+ children: ReactNode;
56
+ /** Custom press handler. The keyboard is also dismissed unless disabled. */
57
+ onPress?: () => void;
58
+ /** Defaults to true. */
59
+ dismissKeyboard?: boolean;
60
+ accessibilityLabel?: string;
61
+ style?: StyleProp<ViewStyle>;
62
+ }
63
+ export interface FocusedInputLayout {
64
+ x: number;
65
+ y: number;
66
+ width: number;
67
+ height: number;
68
+ }
69
+ export {};
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,2 @@
1
+ export declare const DEFAULT_KEYBOARD_SPACING = 16;
2
+ export declare const DEFAULT_ANIMATION_DURATION = 250;
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_ANIMATION_DURATION = exports.DEFAULT_KEYBOARD_SPACING = void 0;
4
+ exports.DEFAULT_KEYBOARD_SPACING = 16;
5
+ exports.DEFAULT_ANIMATION_DURATION = 250;
@@ -0,0 +1,10 @@
1
+ import type { RefObject } from 'react';
2
+ import type { FocusedInputLayout, TextInputRef } from '../types';
3
+ type Measurable = Pick<TextInputRef, 'measureInWindow'>;
4
+ /** Returns the currently focused native TextInput when React Native exposes it. */
5
+ export declare function getFocusedInput(): Measurable | null;
6
+ /** Measure the focused TextInput in window coordinates. */
7
+ export declare function measureFocusedInput(input?: Measurable | null): Promise<FocusedInputLayout | null>;
8
+ /** Request focus on a TextInput ref and return whether a native input was available. */
9
+ export declare function focusInput(input: RefObject<TextInputRef | null>): boolean;
10
+ export {};
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getFocusedInput = getFocusedInput;
4
+ exports.measureFocusedInput = measureFocusedInput;
5
+ exports.focusInput = focusInput;
6
+ const react_native_1 = require("react-native");
7
+ /** Returns the currently focused native TextInput when React Native exposes it. */
8
+ function getFocusedInput() {
9
+ const input = react_native_1.TextInput.State.currentlyFocusedInput?.();
10
+ if (input) {
11
+ return input;
12
+ }
13
+ return null;
14
+ }
15
+ /** Measure the focused TextInput in window coordinates. */
16
+ function measureFocusedInput(input = getFocusedInput()) {
17
+ if (!input?.measureInWindow) {
18
+ return Promise.resolve(null);
19
+ }
20
+ return new Promise(resolve => {
21
+ input.measureInWindow?.((x, y, width, height) => {
22
+ resolve({ x, y, width, height });
23
+ });
24
+ });
25
+ }
26
+ /** Request focus on a TextInput ref and return whether a native input was available. */
27
+ function focusInput(input) {
28
+ const node = input.current;
29
+ if (!node) {
30
+ return false;
31
+ }
32
+ node.focus();
33
+ return true;
34
+ }
@@ -0,0 +1,3 @@
1
+ export { calculateKeyboardOverlap, calculateScrollOffset, } from './scrollManager';
2
+ export { focusInput, getFocusedInput, measureFocusedInput, } from './focusManager';
3
+ export { isAndroid, isIOS } from './platform';
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isIOS = exports.isAndroid = exports.measureFocusedInput = exports.getFocusedInput = exports.focusInput = exports.calculateScrollOffset = exports.calculateKeyboardOverlap = void 0;
4
+ var scrollManager_1 = require("./scrollManager");
5
+ Object.defineProperty(exports, "calculateKeyboardOverlap", { enumerable: true, get: function () { return scrollManager_1.calculateKeyboardOverlap; } });
6
+ Object.defineProperty(exports, "calculateScrollOffset", { enumerable: true, get: function () { return scrollManager_1.calculateScrollOffset; } });
7
+ var focusManager_1 = require("./focusManager");
8
+ Object.defineProperty(exports, "focusInput", { enumerable: true, get: function () { return focusManager_1.focusInput; } });
9
+ Object.defineProperty(exports, "getFocusedInput", { enumerable: true, get: function () { return focusManager_1.getFocusedInput; } });
10
+ Object.defineProperty(exports, "measureFocusedInput", { enumerable: true, get: function () { return focusManager_1.measureFocusedInput; } });
11
+ var platform_1 = require("./platform");
12
+ Object.defineProperty(exports, "isAndroid", { enumerable: true, get: function () { return platform_1.isAndroid; } });
13
+ Object.defineProperty(exports, "isIOS", { enumerable: true, get: function () { return platform_1.isIOS; } });
@@ -0,0 +1,7 @@
1
+ import type { KeyboardState } from '../types';
2
+ /** Subscribe to the shared keyboard event stream. */
3
+ export declare function subscribeKeyboardState(subscriber: () => void): () => void;
4
+ /** Return the stable current snapshot used by `useSyncExternalStore`. */
5
+ export declare function getKeyboardState(): KeyboardState;
6
+ /** Reset the shared state and event listeners; intended for tests. */
7
+ export declare function resetKeyboardStateForTests(): void;
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.subscribeKeyboardState = subscribeKeyboardState;
4
+ exports.getKeyboardState = getKeyboardState;
5
+ exports.resetKeyboardStateForTests = resetKeyboardStateForTests;
6
+ const react_native_1 = require("react-native");
7
+ const constants_1 = require("./constants");
8
+ const platform_1 = require("./platform");
9
+ const initialState = {
10
+ isVisible: false,
11
+ height: 0,
12
+ progress: 0,
13
+ animationDuration: 0,
14
+ };
15
+ let state = initialState;
16
+ let listeners = [];
17
+ const subscribers = new Set();
18
+ function update(event, visible) {
19
+ const coordinates = event.endCoordinates;
20
+ state = {
21
+ isVisible: visible,
22
+ height: visible ? Math.max(0, coordinates?.height ?? 0) : 0,
23
+ progress: visible ? 1 : 0,
24
+ animationDuration: Math.max(0, event.duration ?? constants_1.DEFAULT_ANIMATION_DURATION),
25
+ ...(visible && typeof coordinates?.screenY === 'number'
26
+ ? { screenY: coordinates.screenY }
27
+ : {}),
28
+ };
29
+ subscribers.forEach(subscriber => subscriber());
30
+ }
31
+ function syncCurrentKeyboardState() {
32
+ const metrics = typeof react_native_1.Keyboard.metrics === 'function' ? react_native_1.Keyboard.metrics() : undefined;
33
+ const isVisible = typeof react_native_1.Keyboard.isVisible === 'function'
34
+ ? react_native_1.Keyboard.isVisible()
35
+ : metrics != null;
36
+ if (!isVisible || !metrics) {
37
+ return;
38
+ }
39
+ state = {
40
+ isVisible: true,
41
+ height: metrics.height,
42
+ progress: 1,
43
+ animationDuration: 0,
44
+ screenY: metrics.screenY,
45
+ };
46
+ subscribers.forEach(subscriber => subscriber());
47
+ }
48
+ function startListening() {
49
+ const names = (0, platform_1.getKeyboardEventNames)();
50
+ if (platform_1.isIOS && names.frameChange) {
51
+ listeners = [
52
+ react_native_1.Keyboard.addListener(names.frameChange, event => update(event, event.endCoordinates.height > 0)),
53
+ ];
54
+ syncCurrentKeyboardState();
55
+ return;
56
+ }
57
+ const showSubscription = react_native_1.Keyboard.addListener(names.show, event => update(event, true));
58
+ const hideSubscription = react_native_1.Keyboard.addListener(names.hide, event => update(event, false));
59
+ listeners = [showSubscription, hideSubscription];
60
+ syncCurrentKeyboardState();
61
+ }
62
+ function stopListening() {
63
+ listeners.forEach(subscription => subscription.remove());
64
+ listeners = [];
65
+ }
66
+ /** Subscribe to the shared keyboard event stream. */
67
+ function subscribeKeyboardState(subscriber) {
68
+ subscribers.add(subscriber);
69
+ if (subscribers.size === 1) {
70
+ startListening();
71
+ }
72
+ return () => {
73
+ subscribers.delete(subscriber);
74
+ if (subscribers.size === 0) {
75
+ stopListening();
76
+ }
77
+ };
78
+ }
79
+ /** Return the stable current snapshot used by `useSyncExternalStore`. */
80
+ function getKeyboardState() {
81
+ return state;
82
+ }
83
+ /** Reset the shared state and event listeners; intended for tests. */
84
+ function resetKeyboardStateForTests() {
85
+ stopListening();
86
+ subscribers.clear();
87
+ state = initialState;
88
+ }
@@ -0,0 +1,11 @@
1
+ import type { KeyboardEventName } from 'react-native';
2
+ /** True when running on iOS. */
3
+ export declare const isIOS: boolean;
4
+ /** True when running on Android. */
5
+ export declare const isAndroid: boolean;
6
+ /** Selects the native keyboard event names for the current platform. */
7
+ export declare function getKeyboardEventNames(): {
8
+ show: KeyboardEventName;
9
+ hide: KeyboardEventName;
10
+ frameChange?: KeyboardEventName;
11
+ };
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isAndroid = exports.isIOS = void 0;
4
+ exports.getKeyboardEventNames = getKeyboardEventNames;
5
+ const react_native_1 = require("react-native");
6
+ /** True when running on iOS. */
7
+ exports.isIOS = react_native_1.Platform.OS === 'ios';
8
+ /** True when running on Android. */
9
+ exports.isAndroid = react_native_1.Platform.OS === 'android';
10
+ /** Selects the native keyboard event names for the current platform. */
11
+ function getKeyboardEventNames() {
12
+ return exports.isIOS
13
+ ? {
14
+ show: 'keyboardWillShow',
15
+ hide: 'keyboardWillHide',
16
+ frameChange: 'keyboardWillChangeFrame',
17
+ }
18
+ : { show: 'keyboardDidShow', hide: 'keyboardDidHide' };
19
+ }