UNPKG

react-native-keyboard-controller

Version:

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

297 lines (266 loc) 9.05 kB
import { useEffect, useLayoutEffect } from "react"; import { useEvent, useHandler } from "react-native-reanimated"; import { AndroidSoftInputModes } from "../constants"; import { useKeyboardContext } from "../context"; import { KeyboardController } from "../module"; import type { AnimatedContext, ReanimatedContext } from "../context"; import type { FocusedInputHandler, FocusedInputSelectionChangedEvent, FocusedInputTextChangedEvent, KeyboardHandler, NativeEvent, } from "../types"; /** * Hook that sets the Android soft input mode to adjust resize on mount and * restores default mode on unmount. This ensures the keyboard behavior is consistent * on all Android versions. * * @see {@link https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/keyboard-controller#setinputmode-|Documentation} page for more details. * @example * ```tsx * function MyComponent() { * useResizeMode(); * return <View />; * } * ``` */ export const useResizeMode = () => { useEffect(() => { KeyboardController.setInputMode( AndroidSoftInputModes.SOFT_INPUT_ADJUST_RESIZE, ); return () => KeyboardController.setDefaultMode(); }, []); }; /** * Hook that provides animated (`height`/`progress`) values for tracking keyboard movement. * Automatically sets the resize mode for Android. * * @returns Object {@link AnimatedContext|containing} animated values for keyboard movement. * @see {@link https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/hooks/keyboard/use-keyboard-animation|Documentation} page for more details. * @example * ```tsx * function MyComponent() { * const { height, progress } = useKeyboardAnimation(); * return <Animated.View style={{ transform: [{ translateY: height }] }} />; * } * ``` */ export const useKeyboardAnimation = (): AnimatedContext => { useResizeMode(); const context = useKeyboardContext(); return context.animated; }; /** * Hook that provides reanimated (`height`/`progress`) values for tracking keyboard movement. * Automatically sets the resize mode for Android. * * @returns Object {@link ReanimatedContext|containing} reanimated values for keyboard movement. * @see {@link https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/hooks/keyboard/use-reanimated-keyboard-animation|Documentation} page for more details. * @example * ```tsx * function MyComponent() { * const { height, progress } = useReanimatedKeyboardAnimation(); * return <Reanimated.View style={{ transform: [{ translateY: height }] }} />; * } * ``` */ export const useReanimatedKeyboardAnimation = (): ReanimatedContext => { useResizeMode(); const context = useKeyboardContext(); return context.reanimated; }; /** * An alternative to {@link useKeyboardHandler} that doesn't set resize mode on mount. If your * app already uses `adjustResize`, then you can use this hook instead of `useKeyboardHandler`. * * @param handler - Object containing keyboard event handlers. * @param [deps] - Dependencies array for the effect. * @example * ```tsx * function MyComponent() { * const height = useSharedValue(0); * const progress = useSharedValue(0); * * useGenericKeyboardHandler({ * onMove: (e) => { * "worklet"; * * height.value = e.height; * progress.value = e.progress; * }, * onEnd: (e) => { * "worklet"; * * height.value = e.height; * progress.value = e.progress; * }, * }, []); * * return <Reanimated.View style={{ height: height }] }} />; * } * ``` */ export function useGenericKeyboardHandler( handler: KeyboardHandler, deps?: unknown[], ) { const context = useKeyboardContext(); const { doDependenciesDiffer } = useHandler(handler, deps); const eventHandler = useEvent<NativeEvent>( (event) => { "worklet"; if (event.eventName.endsWith("onKeyboardMoveStart")) { handler.onStart?.(event); } if (event.eventName.endsWith("onKeyboardMove")) { handler.onMove?.(event); } if (event.eventName.endsWith("onKeyboardMoveEnd")) { handler.onEnd?.(event); } if (event.eventName.endsWith("onKeyboardMoveInteractive")) { handler.onInteractive?.(event); } }, [ "onKeyboardMoveStart", "onKeyboardMove", "onKeyboardMoveEnd", "onKeyboardMoveInteractive", ], doDependenciesDiffer, ); useLayoutEffect(() => { const cleanup = context.setKeyboardHandlers(eventHandler); return () => cleanup(); }, deps); } /** * Hook that gives an access to each aspect of keyboard movement with workletized `onStart`/`onMove`/`onInteractive`/`onEnd` handlers. * * @param handler - Object containing keyboard event handlers. * @param [deps] - Dependencies array for the effect. * @see {@link https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/hooks/keyboard/use-keyboard-handler|Documentation} page for more details. * @example * ```tsx * function MyComponent() { * const height = useSharedValue(0); * const progress = useSharedValue(0); * * useKeyboardHandler({ * onMove: (e) => { * "worklet"; * * height.value = e.height; * progress.value = e.progress; * }, * onEnd: (e) => { * "worklet"; * * height.value = e.height; * progress.value = e.progress; * }, * }, []); * * return <Reanimated.View style={{ height: height }] }} />; * } * ``` */ export function useKeyboardHandler(handler: KeyboardHandler, deps?: unknown[]) { useResizeMode(); useGenericKeyboardHandler(handler, deps); } /** * Hook for controlling keyboard controller module. * Allows to disable/enable it and check the actual state (whether it's enabled or not). * When disabled it fallbacks to default android keyboard handling and stops tracking all * the events that are exposed from this library. * * @property {Function} setEnabled - Function to enable/disable keyboard handling. * @property {boolean} enabled - Current enabled state. * @returns Object containing keyboard control functions and state. * @see {@link https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/hooks/module/use-keyboard-controller|Documentation} page for more details. * @example * ```tsx * function MyComponent() { * const { setEnabled, enabled } = useKeyboardController(); * return ( * <Button * title={enabled ? 'Disable' : 'Enable'} * onPress={() => setEnabled(!enabled)} * /> * ); * } * ``` */ export function useKeyboardController() { const context = useKeyboardContext(); return { setEnabled: context.setEnabled, enabled: context.enabled }; } /** * Hook that provides access to the layout of the currently focused input. * * @returns Object containing reanimated values for focused input. * @see {@link https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/hooks/input/use-reanimated-focused-input|Documentation} page for more details. * @example * ```tsx * function MyComponent() { * const { input } = useReanimatedFocusedInput(); * return <Reanimated.View style={{ height: input.value?.layout.height }} />; * } * ``` */ export function useReanimatedFocusedInput() { const context = useKeyboardContext(); return { input: context.layout, update: context.update, }; } /** * Hook for handling focused input events, such as changes of selection, text etc. * * @param handler - Object containing focused input event handlers. * @param [deps] - Dependencies array for the effect. * @see {@link https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/hooks/input/use-focused-input-handler|Documentation} page for more details. * @example * ```tsx * function MyComponent() { * useFocusedInputHandler({ * onChangeText: (e) => console.log('Text changed:', e.text), * onSelectionChange: (e) => console.log('Selection changed:', e.selection) * }); * return <View />; * } * ``` */ export function useFocusedInputHandler( handler: FocusedInputHandler, deps?: unknown[], ) { const context = useKeyboardContext(); const { doDependenciesDiffer } = useHandler<never, never>(handler, deps); const eventHandler = useEvent< FocusedInputSelectionChangedEvent | FocusedInputTextChangedEvent >( (event) => { "worklet"; if (event.eventName.endsWith("onFocusedInputTextChanged")) { handler.onChangeText?.(event as FocusedInputTextChangedEvent); } if (event.eventName.endsWith("onFocusedInputSelectionChanged")) { handler.onSelectionChange?.(event as FocusedInputSelectionChangedEvent); } }, ["onFocusedInputTextChanged", "onFocusedInputSelectionChanged"], doDependenciesDiffer, ); useLayoutEffect(() => { const cleanup = context.setInputHandlers(eventHandler); return () => cleanup(); }, deps); } export * from "./useWindowDimensions"; export * from "./useKeyboardState";