From 5fa0d788939363ad48c56d8433b78382e1209bd4 Mon Sep 17 00:00:00 2001 From: iway1 Date: Sun, 28 Aug 2022 21:00:23 -0500 Subject: [PATCH] Added easing prop, refactored default values and common props to a shared type --- App.tsx | 1 + README.md | 38 +++++++++++-------- .../src/components/KeyboardAvoiderInsets.tsx | 31 +++++++-------- .../components/KeyboardAvoiderScrollView.tsx | 26 ++++--------- .../src/components/KeyboardAvoiderView.tsx | 31 ++++++--------- package/src/components/common-props.ts | 23 +++++++++++ package/src/defaults.ts | 4 +- package/src/utilities.ts | 10 ++--- 8 files changed, 86 insertions(+), 78 deletions(-) create mode 100644 package/src/components/common-props.ts diff --git a/App.tsx b/App.tsx index cc00911..8b03e55 100644 --- a/App.tsx +++ b/App.tsx @@ -8,6 +8,7 @@ import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view import KeyboardAvoiderScrollSection from './package/src/components/KeyboardAvoiderScrollSection'; import ReactNativeExample, { ReactNativeFixedExample } from './examples/react-native'; import { textInputStyle } from './examples/styles'; +import { Easing } from 'react-native-reanimated'; const inputStyle: ViewStyle = { padding: 50, diff --git a/README.md b/README.md index c3de7af..8bc3959 100644 --- a/README.md +++ b/README.md @@ -276,28 +276,34 @@ For developer convenience, this repo is a React Native project. You can just pul ## Props +### Common props +All of the components support the following props: + +| **Props** | **Type** | **Description** | **Default** | +|-----------------|-------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------| +| animationTime | number | Time to move the view out of the way when the keyboard opens. | 150 | +| animationEasing | Animated.EasingFunction | Easing function to use for both opening and closing animation.
Opening animation will use `Easing.out(animationEasing)`, while closing animation
will use `Easing.in(animationEasing)`. | Easing.quad | +| extraSpace | number | How much space there should be between the keyboard avoiding element and the keyboard. | 20 | + ### `KeyboardAvoiderView` Props -All `View` props will be passed. Additionally, the following props can be passed: +Supports all React Native `View` props as well as all [common props](#common-keyboardavoider-props). + +Additionally, it supports the following props + +| **Props** | **Type** | **Description** | **Default** | +|---------------|---------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------| +| avoidMode | 'whole-view'
'focused-input' | Which avoid mode to use.
'whole-view' will move the entire view out of the way.
'focused-input' will move just enough to show the input plus the extra space. | 'whole-view' | +| enableAndroid | boolean | Enable keyboard avoiding on Android. | true | -| **Prop** | **Type** | **Description** | **Default** | -|---------------|---------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------| -| animationTime | number | Time to move the view out of the way when the keyboard opens. | 150 | -| avoidMode | 'whole-view'
'focused-input' | Which avoid mode to use.
'whole-view' will move the entire `KeyboardAvoidingView` out of the way.
'focused-input' will only show the focused input. | 'whole-view' | -| enableAndroid | boolean | Enable keyboard avoiding on android. | true | -| extraSpace | number | How much space there should be between the keyboard avoiding element and the keyboard. | 20 | ### `KeyboardAvoiderScrollView` Props -All `ScrollView` props can be passed. Additionally, the following props can be passed: +Supports all React Native `ScrollView` props as well as all [common props](#common-keyboardavoider-props). + +Additionally, it supports the following props | **Props** | **Type** | **Description** | **Default** | |-----------------|--------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------| -| extraSpace | number | How much space there should be between the keyboard avoiding element and the keyboard. | 20 | -| animationTime | number | Time to move the view out of the way when the keyboard opens. | 150 | -| iosHideBehavior | 'stay'
'revert' | What to do when the keyboard hides on iOS.
'stay' makes it where the scrollview stays where it is when the keyboard closes.
'revert' makes it where the scrollview returns to its original position. | 'stay' | +| iosHideBehavior | 'stay'
'revert' | Behavior when the keyboard hides on iOS.
'stay' makes it where the scroll view stays where it is when the keyboard closes.
'revert' makes it where the scroll view returns to its original position. | 'stay' | ### `KeyboardAvoiderInsets` Props - -| **Props** | **Type** | **Description** | **Default** | -|---------------|----------|----------------------------------------------------------------------------------------|-------------| -| extraSpace | number | How much space there should be between the keyboard avoiding element and the keyboard. | 20 | -| animationTime | number | Time to move the view out of the way when the keyboard opens. | 150 | +Supports all [common props](#common-keyboardavoider-props). \ No newline at end of file diff --git a/package/src/components/KeyboardAvoiderInsets.tsx b/package/src/components/KeyboardAvoiderInsets.tsx index d3af80b..f360a2d 100644 --- a/package/src/components/KeyboardAvoiderInsets.tsx +++ b/package/src/components/KeyboardAvoiderInsets.tsx @@ -1,24 +1,16 @@ import React from "react"; import { useRef } from "react"; import { Platform } from "react-native"; -import Animated, { useAnimatedStyle, useSharedValue, withTiming } from "react-native-reanimated"; -import { DEFAULT_ANIMATION_TIME, DEFAULT_EXTRA_SPACE } from "../defaults"; +import Animated, { EasingFn, useAnimatedStyle, useSharedValue, withTiming } from "react-native-reanimated"; import { useKeyboardHandlers } from "../hooks"; import { calcAndroidSystemPan, closeAnimation, measureFocusedInputBottomY, openAnimation } from "../utilities"; +import { CommonProps, defaultCommonProps } from "./common-props"; export default function KeyboardAvoiderInsets({ - animationTime = DEFAULT_ANIMATION_TIME, - extraSpace = DEFAULT_EXTRA_SPACE, -}: { - /** - * Duration of the keyboard avoiding animation. - */ - animationTime?: number, - /** - * Extra space between the keyboard avoiding element and the keyboard. - */ - extraSpace?: number, -}) { + animationEasing=defaultCommonProps.animationEasing, + animationTime=defaultCommonProps.animationTime, + extraSpace=defaultCommonProps.extraSpace +}:CommonProps) { const heightAnimatedValue = useSharedValue(0); const animatedRef = useRef(null); @@ -31,7 +23,7 @@ export default function KeyboardAvoiderInsets({ useKeyboardHandlers({ showHandler: (e) => { if (Platform.OS == 'android') { - measureFocusedInputBottomY((inputBottomY)=>{ + measureFocusedInputBottomY((inputBottomY) => { const systemPan = calcAndroidSystemPan({ inputBottomY, keyboardEndY: e.endCoordinates.screenY, @@ -39,7 +31,10 @@ export default function KeyboardAvoiderInsets({ animatedRef.current?.measure((x, y, width, height, pageX, pageY) => { const delta = Math.max(0, (pageY + extraSpace) - e.endCoordinates.screenY - systemPan); if (delta) { - heightAnimatedValue.value = withTiming(delta, openAnimation(animationTime)) + heightAnimatedValue.value = withTiming(delta, { + easing: animationEasing, + duration: animationTime + }) } }) }) @@ -47,14 +42,14 @@ export default function KeyboardAvoiderInsets({ animatedRef.current?.measure((x, y, width, height, pageX, pageY) => { const delta = Math.max(0, (pageY + extraSpace) - e.endCoordinates.screenY); if (delta) { - heightAnimatedValue.value = withTiming(delta, openAnimation(animationTime)) + heightAnimatedValue.value = withTiming(delta, openAnimation(animationTime, animationEasing)) } }) } }, hideHandler: () => { - heightAnimatedValue.value = withTiming(0, closeAnimation(animationTime)) + heightAnimatedValue.value = withTiming(0, closeAnimation(animationTime, animationEasing)) } }) diff --git a/package/src/components/KeyboardAvoiderScrollView.tsx b/package/src/components/KeyboardAvoiderScrollView.tsx index d754c34..9b5ecea 100644 --- a/package/src/components/KeyboardAvoiderScrollView.tsx +++ b/package/src/components/KeyboardAvoiderScrollView.tsx @@ -1,27 +1,16 @@ import React, { createContext, useContext, useMemo, useRef, useState } from "react"; import { KeyboardEventListener, NativeScrollEvent, NativeSyntheticEvent, Platform, ScrollViewProps, View, ScrollView } from "react-native"; -import Animated, { Easing, Layout, scrollTo, useAnimatedRef, useAnimatedStyle, useDerivedValue, useSharedValue, withTiming } from "react-native-reanimated"; -import { DEFAULT_ANIMATION_TIME, DEFAULT_EXTRA_SPACE } from "../defaults"; +import Animated, { scrollTo, useAnimatedRef, useAnimatedStyle, useDerivedValue, useSharedValue, withTiming } from "react-native-reanimated"; import { useKeyboardHandlers } from "../hooks"; import { closeAnimation, measureFocusedInputBottomYAsync } from "../utilities"; +import { CommonProps, defaultCommonProps } from "./common-props"; const ScrollContext = createContext<{ registerView: ({view, id,}:{view: View, id: string}) => void, unregisterView: (id: string)=>void, } | null>(null); -interface Props extends ScrollViewProps { - /** - * Extra space between the keyboard and the keyboard avoiding element. - * Defaults to 20. - */ - extraSpace?: number, - - /** - * Duration of the keyboard avoiding animation. - */ - animationTime?: number, - +type Props = ScrollViewProps & CommonProps & { /** * What to do when the keyboard hides on iOS. * @option 'stay' - *Default* scroll view will not move when the keyboard hides (it will stay where it is.) @@ -39,8 +28,9 @@ async function measureView(view: View) { } export default function KeyboardAvoiderScrollView({ - extraSpace = DEFAULT_EXTRA_SPACE, - animationTime=DEFAULT_ANIMATION_TIME, + animationEasing=defaultCommonProps.animationEasing, + animationTime=defaultCommonProps.animationTime, + extraSpace=defaultCommonProps.extraSpace, iosHideBehavior='stay', ...props }: Props) { @@ -105,7 +95,7 @@ export default function KeyboardAvoiderScrollView({ function handleKeyboardWillHide() { if(Platform.OS == 'android' || iosHideBehavior == 'revert') { - yTranslate.value = withTiming(0, closeAnimation(animationTime)) + yTranslate.value = withTiming(0, closeAnimation(animationTime, animationEasing)) return; } const scrollsToAdjustedForTranslate = currentScroll.current - yTranslate.value; @@ -114,7 +104,7 @@ export default function KeyboardAvoiderScrollView({ if(scrollsToAdjustedForTranslate >= scrollMax.current) { // In cases where there is no room to actually scroll the scroll view we just animate back to the // start position. - yTranslate.value = withTiming(0, closeAnimation(animationTime)) + yTranslate.value = withTiming(0, closeAnimation(animationTime, animationEasing)) return; } diff --git a/package/src/components/KeyboardAvoiderView.tsx b/package/src/components/KeyboardAvoiderView.tsx index 5487b6f..e877cb2 100644 --- a/package/src/components/KeyboardAvoiderView.tsx +++ b/package/src/components/KeyboardAvoiderView.tsx @@ -13,31 +13,24 @@ import Animated, { useSharedValue, withTiming, } from 'react-native-reanimated' -import { DEFAULT_ANIMATION_TIME, DEFAULT_EXTRA_SPACE } from '../defaults' import { useKeyboardHandlers } from '../hooks' import { closeAnimation, measureFocusedInputBottomY, openAnimation } from '../utilities' +import { CommonProps, defaultCommonProps } from './common-props' export type KeyboardAvoidMode = 'whole-view' | 'focused-input'; export default function KeyboardAvoiderView({ + animationEasing = defaultCommonProps.animationEasing, + animationTime = defaultCommonProps.animationTime, + extraSpace = defaultCommonProps.extraSpace, enableAndroid = true, - animationTime = DEFAULT_ANIMATION_TIME, - extraSpace = DEFAULT_EXTRA_SPACE, avoidMode = 'whole-view', ...props -}: ComponentProps & { +}: ComponentProps & CommonProps & { /** * Enable on android. Defaults to true to ensure consistent behavior. */ enableAndroid?: boolean, - /** - * Sets the duration of the keyboard avoiding animation. - */ - animationTime?: number, - /** - * Extra space between the keyboard avoiding element and the keyboard. - */ - extraSpace?: number, /** * Sets the avoid mode. Defaults to 'whole-view'. * @option 'whole-view' - view moves to show the entire view when the keyboard is shown. @@ -52,12 +45,12 @@ export default function KeyboardAvoiderView({ const handleKeyboardWillShow: KeyboardEventListener = (e) => { keyboardTopRef.current = e.endCoordinates.screenY - + if (Platform.OS == 'android') { measureFocusedInputBottomY((inputBottomY) => { const pannedBy = Math.max(inputBottomY - e.endCoordinates.screenY, 0); - if(avoidMode=='focused-input') { - setScrollToElementBottomY(inputBottomY-pannedBy) + if (avoidMode == 'focused-input') { + setScrollToElementBottomY(inputBottomY - pannedBy) return; } ref.current?.measure((x, y, w, viewHeight, px, viewPageY) => { @@ -79,7 +72,7 @@ export default function KeyboardAvoiderView({ }) } } - + function handleKeyboardWillHide() { setScrollToElementBottomY(null) } @@ -91,18 +84,18 @@ export default function KeyboardAvoiderView({ }, [avoidMode, extraSpace, animationTime]) function pos(scrollTo: number) { - return scrollTo - keyboardTopRef.current + DEFAULT_EXTRA_SPACE + return scrollTo - keyboardTopRef.current + extraSpace } useEffect(() => { if (scrollToElementBottomY === null) { - animation.value = withTiming(0, closeAnimation(animationTime)) + animation.value = withTiming(0, closeAnimation(animationTime, animationEasing)) return } const p = pos(scrollToElementBottomY); if (p <= 0) return if (animation.value !== p) { - animation.value = withTiming(p, openAnimation(animationTime)) + animation.value = withTiming(p, openAnimation(animationTime, animationEasing)) } }, [scrollToElementBottomY]) diff --git a/package/src/components/common-props.ts b/package/src/components/common-props.ts new file mode 100644 index 0000000..2e44ccb --- /dev/null +++ b/package/src/components/common-props.ts @@ -0,0 +1,23 @@ +import Animated, { Easing } from "react-native-reanimated" + +export type CommonProps = { + /** + * Duration of the keyboard avoiding animation. + */ + animationTime?: number, + /** + * Extra space between the keyboard avoiding element and the keyboard. + */ + extraSpace?: number, + /** + * Easing function to use. Can be any `react-native-reanimated` easing function. Defaults to Easing.quad. + * Open animation will use Easing.out(animationEasing), close animation will use Easing.in(animationEasing). + */ + animationEasing?: Animated.EasingFunction +} + +export const defaultCommonProps = { + animationTime: 150, + animationEasing: Easing.quad, + extraSpace: 20, +} \ No newline at end of file diff --git a/package/src/defaults.ts b/package/src/defaults.ts index fc449e8..848e7a0 100644 --- a/package/src/defaults.ts +++ b/package/src/defaults.ts @@ -1,2 +1,2 @@ -export const DEFAULT_EXTRA_SPACE = 20 -export const DEFAULT_ANIMATION_TIME = 150 \ No newline at end of file +import { Easing } from "react-native-reanimated" + diff --git a/package/src/utilities.ts b/package/src/utilities.ts index 1a2635b..bb3537b 100644 --- a/package/src/utilities.ts +++ b/package/src/utilities.ts @@ -1,5 +1,5 @@ import { TextInput } from "react-native" -import { Easing } from "react-native-reanimated"; +import Animated, { Easing } from "react-native-reanimated"; export async function measureFocusedInputBottomYAsync() { return new Promise(resolve=>{ @@ -36,16 +36,16 @@ export function calcAndroidSystemPan({ return Math.max(0, delta); } -export function closeAnimation(duration: number) { +export function closeAnimation(duration: number, easing: Animated.EasingFunction) { return { duration: duration + 50, - easing: Easing.in(Easing.ease) + easing: Easing.in(easing) } } -export function openAnimation(duration: number) { +export function openAnimation(duration: number, easing: Animated.EasingFunction) { return { duration, - easing: Easing.inOut(Easing.ease) + easing: Easing.out(easing) } } \ No newline at end of file