UNPKG

react-native-keyboard-controller

Version:

Keyboard manager which works in identical way on both iOS and Android

180 lines (173 loc) 6.37 kB
/* eslint react/jsx-sort-props: off */ import React, { useEffect, useMemo, useRef, useState } from "react"; import { Animated, Platform, StyleSheet } from "react-native"; import { controlEdgeToEdgeValues, isEdgeToEdge } from "react-native-is-edge-to-edge"; import Reanimated, { useSharedValue } from "react-native-reanimated"; import { KeyboardControllerView } from "./bindings"; import { KeyboardContext } from "./context"; import { focusedInputEventsMap, keyboardEventsMap } from "./event-mappings"; import { useAnimatedValue, useEventHandlerRegistration } from "./internal"; import { KeyboardController } from "./module"; import { useAnimatedKeyboardHandler, useFocusedInputLayoutHandler } from "./reanimated"; const IS_EDGE_TO_EDGE = isEdgeToEdge(); const KeyboardControllerViewAnimated = Reanimated.createAnimatedComponent(Animated.createAnimatedComponent(KeyboardControllerView)); const styles = StyleSheet.create({ container: { flex: 1 }, hidden: { display: "none", position: "absolute" } }); // capture `Platform.OS` in separate variable to avoid deep workletization of entire RN package // see https://github.com/kirillzyusko/react-native-keyboard-controller/issues/393 and https://github.com/kirillzyusko/react-native-keyboard-controller/issues/294 for more details const OS = Platform.OS; /** * A component that wrap your app. Under the hood it works with {@link https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/keyboard-controller-view|KeyboardControllerView} to receive events during keyboard movements, * maps these events to `Animated`/`Reanimated` values and store them in context. * * @param props - Provider props, such as `statusBarTranslucent`, `navigationBarTranslucent`, etc. * @returns A component that should be mounted in root of your App layout. * @see {@link https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/keyboard-provider|Documentation} page for more details. * @example * ```tsx * <KeyboardProvider> * <NavigationContainer /> * </KeyboardProvider> * ``` */ export const KeyboardProvider = props => { const { children, statusBarTranslucent, navigationBarTranslucent, preserveEdgeToEdge, enabled: initiallyEnabled = true, preload = true } = props; // ref const viewTagRef = useRef(null); // state const [enabled, setEnabled] = useState(initiallyEnabled); // animated values const progress = useAnimatedValue(0); const height = useAnimatedValue(0); // shared values const progressSV = useSharedValue(0); const heightSV = useSharedValue(0); const layout = useSharedValue(null); const setKeyboardHandlers = useEventHandlerRegistration(keyboardEventsMap, viewTagRef); const setInputHandlers = useEventHandlerRegistration(focusedInputEventsMap, viewTagRef); // memo const context = useMemo(() => ({ enabled, animated: { progress: progress, height: Animated.multiply(height, -1) }, reanimated: { progress: progressSV, height: heightSV }, layout, setKeyboardHandlers, setInputHandlers, setEnabled }), [enabled]); const style = useMemo(() => [styles.hidden, { transform: [{ translateX: height }, { translateY: progress }] }], []); const onKeyboardMove = useMemo(() => Animated.event([{ nativeEvent: { progress, height } }], // Setting useNativeDriver to true on web triggers a warning due to the absence of a native driver for web. Therefore, it is set to false. { useNativeDriver: Platform.OS !== "web" }), []); // handlers const updateSharedValues = (event, platforms) => { "worklet"; if (platforms.includes(OS)) { // eslint-disable-next-line react-compiler/react-compiler progressSV.value = event.progress; heightSV.value = -event.height; } }; const keyboardHandler = useAnimatedKeyboardHandler({ onKeyboardMoveStart: event => { "worklet"; updateSharedValues(event, ["ios"]); }, onKeyboardMove: event => { "worklet"; updateSharedValues(event, ["android"]); }, onKeyboardMoveInteractive: event => { "worklet"; updateSharedValues(event, ["android", "ios"]); }, onKeyboardMoveEnd: event => { "worklet"; updateSharedValues(event, ["android"]); } }, []); const inputLayoutHandler = useFocusedInputLayoutHandler({ onFocusedInputLayoutChanged: e => { "worklet"; if (e.target !== -1) { layout.value = e; } else { layout.value = null; } } }, []); useEffect(() => { if (preload) { KeyboardController.preload(); } }, [preload]); if (__DEV__) { controlEdgeToEdgeValues({ statusBarTranslucent, navigationBarTranslucent, preserveEdgeToEdge }); } return /*#__PURE__*/React.createElement(KeyboardContext.Provider, { value: context }, /*#__PURE__*/React.createElement(KeyboardControllerViewAnimated, { ref: viewTagRef, enabled: enabled, navigationBarTranslucent: IS_EDGE_TO_EDGE || navigationBarTranslucent, statusBarTranslucent: IS_EDGE_TO_EDGE || statusBarTranslucent, preserveEdgeToEdge: IS_EDGE_TO_EDGE || preserveEdgeToEdge, style: styles.container // on*Reanimated prop must precede animated handlers to work correctly , onKeyboardMoveReanimated: keyboardHandler, onKeyboardMoveStart: OS === "ios" ? onKeyboardMove : undefined, onKeyboardMove: OS === "android" ? onKeyboardMove : undefined, onKeyboardMoveInteractive: onKeyboardMove, onKeyboardMoveEnd: OS === "android" ? onKeyboardMove : undefined, onFocusedInputLayoutChangedReanimated: inputLayoutHandler }, children), /*#__PURE__*/React.createElement(Animated.View, { // we are using this small hack, because if the component (where // animated value has been used) is unmounted, then animation will // stop receiving events (seems like it's react-native optimization). // So we need to keep a reference to the animated value, to keep it's // always mounted (keep a reference to an animated value). // // To test why it's needed, try to open screen which consumes Animated.Value // then close it and open it again (for example 'Animated transition'). style: style })); }; //# sourceMappingURL=animated.js.map