diff --git a/example/src/App.tsx b/example/src/App.tsx
index a527dbd..c0fec14 100644
--- a/example/src/App.tsx
+++ b/example/src/App.tsx
@@ -1,8 +1,9 @@
import * as React from 'react';
-import { StyleSheet, View, Text, ScrollView } from 'react-native';
+import { StyleSheet, View, Text } from 'react-native';
import { TextInput } from './components/TextInput';
import { ContentCard } from './components/ContentCard';
+import { KeyboardScrollView } from 'react-native-keyboard-scrollview';
export default function App() {
return (
@@ -10,11 +11,14 @@ export default function App() {
Example app
-
+
-
+
);
}
diff --git a/src/KeyboardScrollView.tsx b/src/KeyboardScrollView.tsx
new file mode 100644
index 0000000..236b676
--- /dev/null
+++ b/src/KeyboardScrollView.tsx
@@ -0,0 +1,134 @@
+import React, {
+ useCallback,
+ useEffect,
+ useMemo,
+ useRef,
+ useState,
+} from 'react';
+import {
+ Keyboard,
+ ScrollView,
+ TextInput,
+ StatusBar,
+ StyleSheet,
+} from 'react-native';
+
+interface Props extends React.ComponentProps {
+ additionalScrollHeight?: number;
+}
+
+export const KeyboardScrollView = ({
+ children,
+ additionalScrollHeight,
+ ...props
+}: Props) => {
+ const scrollViewRef = useRef(null);
+ const scrollPositionRef = useRef(0);
+ const scrollContentSizeRef = useRef(0);
+ const scrollViewSizeRef = useRef(0);
+
+ const [isKeyboardVisible, setIsKeyboardVisible] = useState(false);
+ const [additionalPadding, setAdditionalPadding] = useState(0);
+
+ const scrollToPosition = useCallback(
+ (toPosition: number, animated?: boolean) => {
+ scrollViewRef.current?.scrollTo({ y: toPosition, animated: !!animated });
+ scrollPositionRef.current = toPosition;
+ },
+ []
+ );
+
+ const additionalScroll = useMemo(
+ () => additionalScrollHeight ?? 0,
+ [additionalScrollHeight]
+ );
+ const androidStatusBarOffset = useMemo(
+ () => StatusBar.currentHeight ?? 0,
+ []
+ );
+
+ useEffect(() => {
+ Keyboard.addListener('keyboardDidShow', (frames) => {
+ const keyboardY = frames.endCoordinates.screenY;
+ const keyboardHeight = frames.endCoordinates.height;
+ setAdditionalPadding(keyboardHeight);
+ setTimeout(() => {
+ setIsKeyboardVisible(true);
+ }, 100);
+
+ const currentlyFocusedInput = TextInput.State.currentlyFocusedInput();
+ const currentScrollY = scrollPositionRef.current;
+
+ currentlyFocusedInput.measureInWindow((_x, y, _width, height) => {
+ const endOfInputY = y + height + androidStatusBarOffset;
+ const deltaToScroll = endOfInputY - keyboardY;
+
+ if (deltaToScroll < 0) return;
+
+ const scrollPositionTarget =
+ currentScrollY + deltaToScroll + additionalScroll;
+ scrollToPosition(scrollPositionTarget, true);
+ });
+ });
+
+ Keyboard.addListener('keyboardDidHide', () => {
+ setAdditionalPadding(0);
+ setIsKeyboardVisible(false);
+ });
+
+ Keyboard.addListener('keyboardWillHide', (frames) => {
+ // iOS only, scroll back to initial position to avoid flickering
+ const keyboardHeight = frames.endCoordinates.height;
+ const currentScrollY = scrollPositionRef.current;
+ const scrollPositionTarget = currentScrollY - keyboardHeight;
+ scrollToPosition(scrollPositionTarget, true);
+ });
+
+ return () => {
+ Keyboard.removeAllListeners('keyboardDidShow');
+ Keyboard.removeAllListeners('keyboardDidHide');
+ Keyboard.removeAllListeners('keyboardWillHide');
+ };
+ }, [additionalScroll, androidStatusBarOffset, scrollToPosition]);
+
+ return (
+ {
+ scrollPositionRef.current = event.nativeEvent.contentOffset.y;
+ }}
+ onScrollEndDrag={(event) => {
+ scrollPositionRef.current = event.nativeEvent.contentOffset.y;
+ }}
+ onLayout={(event) => {
+ scrollViewSizeRef.current = event.nativeEvent.layout.height;
+ }}
+ onContentSizeChange={(_width, height) => {
+ const currentContentHeight = scrollContentSizeRef.current;
+ const contentSizeDelta = height - currentContentHeight;
+ scrollContentSizeRef.current = height;
+
+ if (!isKeyboardVisible) return;
+
+ const currentScrollY = scrollPositionRef.current;
+ const scrollPositionTarget = currentScrollY + contentSizeDelta;
+ scrollToPosition(scrollPositionTarget, true);
+ }}
+ {...props}
+ >
+ {children}
+
+ );
+};
+
+const styles = StyleSheet.create({
+ contentContainer: {
+ flexGrow: 1,
+ backgroundColor: 'pink',
+ },
+});
diff --git a/src/__tests__/index.test.tsx b/src/__tests__/index.test.tsx
deleted file mode 100644
index bf84291..0000000
--- a/src/__tests__/index.test.tsx
+++ /dev/null
@@ -1 +0,0 @@
-it.todo('write a test');
diff --git a/src/index.tsx b/src/index.tsx
index 9e42cf3..3c0ca20 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -1,3 +1 @@
-export function multiply(a: number, b: number): Promise {
- return Promise.resolve(a * b);
-}
+export { KeyboardScrollView } from './KeyboardScrollView';