/* eslint react/jsx-sort-props: off */ import React, { useCallback, 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 { FocusedInputEvents, KeyboardControllerView, KeyboardControllerViewCommands, } from "./bindings"; import { KeyboardContext } from "./context"; import { useAnimatedValue, useEventHandlerRegistration } from "./internal"; import { KeyboardController } from "./module"; import { useAnimatedKeyboardHandler, useFocusedInputLayoutHandler, } from "./reanimated"; import type { KeyboardAnimationContext } from "./context"; import type { FocusedInputLayoutChangedEvent, KeyboardControllerProps, KeyboardProviderProps, NativeEvent, } from "./types"; import type { ViewStyle } from "react-native"; const IS_EDGE_TO_EDGE = isEdgeToEdge(); const KeyboardControllerViewAnimated = Reanimated.createAnimatedComponent( Animated.createAnimatedComponent(KeyboardControllerView), ); type Styles = { container: ViewStyle; hidden: ViewStyle; }; 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 * * * * ``` */ export const KeyboardProvider = (props: KeyboardProviderProps) => { const { children, statusBarTranslucent, navigationBarTranslucent, preserveEdgeToEdge, enabled: initiallyEnabled = true, preload = true, } = props; // ref const viewRef = 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(viewRef); const setInputHandlers = useEventHandlerRegistration(viewRef); const update = useCallback(async () => { KeyboardControllerViewCommands.synchronizeFocusedInputLayout( viewRef.current, ); await new Promise((resolve) => { const subscription = FocusedInputEvents.addListener( "layoutDidSynchronize", () => { subscription.remove(); resolve(null); }, ); }); }, []); // memo const context = useMemo( () => ({ enabled, animated: { progress: progress, height: Animated.multiply(height, -1) }, reanimated: { progress: progressSV, height: heightSV }, layout, update, 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: NativeEvent, platforms: string[]) => { "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: NativeEvent) => { "worklet"; updateSharedValues(event, ["ios"]); }, onKeyboardMove: (event: NativeEvent) => { "worklet"; updateSharedValues(event, ["android"]); }, onKeyboardMoveInteractive: (event: NativeEvent) => { "worklet"; updateSharedValues(event, ["android", "ios"]); }, onKeyboardMoveEnd: (event: NativeEvent) => { "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 ( {children} ); };