UNPKG

react-native-keyboard-controller

Version:

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

149 lines (140 loc) 6.19 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.useChatKeyboard = useChatKeyboard; var _reactNativeReanimated = require("react-native-reanimated"); var _hooks = require("../../../hooks"); var _useScrollState = _interopRequireDefault(require("../../hooks/useScrollState")); var _helpers = require("./helpers"); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } /** * Hook that manages keyboard-driven scrolling for chat-style scroll views. * Calculates padding (extra scrollable space) and content shift values, * using iOS-specific strategy (contentOffset set once in onStart). * * @param scrollViewRef - Animated ref to the scroll view. * @param options - Configuration for inverted and keyboardLiftBehavior. * @returns Shared values for padding and contentOffsetY. * @example * ```tsx * const { padding, contentOffsetY } = useChatKeyboard(ref, { * inverted: false, * keyboardLiftBehavior: "always", * }); * ``` */ function useChatKeyboard(scrollViewRef, options) { const { inverted, keyboardLiftBehavior, freeze, offset, blankSpace, extraContentPadding } = options; const padding = (0, _reactNativeReanimated.useSharedValue)(0); const currentHeight = (0, _reactNativeReanimated.useSharedValue)(0); const contentOffsetY = (0, _reactNativeReanimated.useSharedValue)(0); const targetKeyboardHeight = (0, _reactNativeReanimated.useSharedValue)(0); const prevAbsorption = (0, _reactNativeReanimated.useSharedValue)(0); const { layout, size, offset: scroll, onLayout, onContentSizeChange } = (0, _useScrollState.default)(scrollViewRef); (0, _hooks.useKeyboardHandler)({ onStart: e => { "worklet"; if (freeze.value) { return; } if (e.height > 0) { // eslint-disable-next-line react-compiler/react-compiler targetKeyboardHeight.value = e.height; } const effective = (0, _helpers.getEffectiveHeight)(e.height, targetKeyboardHeight.value, offset); const atEnd = (0, _helpers.isScrollAtEnd)(scroll.value, layout.value.height, size.value.height, inverted); // Scale minimum padding absorption by how much of it is visible. // Fully visible → full absorption; fully off-screen → no absorption. const visibleFraction = (0, _helpers.getVisibleMinimumPaddingFraction)(scroll.value, layout.value.height, size.value.height, blankSpace.value, inverted); const visiblePadding = visibleFraction * blankSpace.value; const minimumPaddingAbsorbed = Math.max(0, visiblePadding - extraContentPadding.value); const scrollEffective = (0, _helpers.getScrollEffective)(effective, minimumPaddingAbsorbed); const actualTotalPadding = Math.max(blankSpace.value, effective + extraContentPadding.value); // persistent mode: when keyboard shrinks, clamp to valid range if (keyboardLiftBehavior === "persistent" && effective < padding.value) { padding.value = effective; prevAbsorption.value = minimumPaddingAbsorbed; if (inverted) { const maxScroll = Math.max(size.value.height - layout.value.height, 0); contentOffsetY.value = Math.max(-actualTotalPadding, Math.min(scroll.value, maxScroll)); } else { const maxScroll = Math.max(size.value.height - layout.value.height + actualTotalPadding, 0); contentOffsetY.value = Math.max(0, Math.min(scroll.value, maxScroll)); } return; } // never mode: when keyboard shrinks, clamp to valid range // to avoid ghost padding if (keyboardLiftBehavior === "never" && effective < padding.value && atEnd) { padding.value = effective; prevAbsorption.value = minimumPaddingAbsorbed; if (inverted) { const maxScroll = Math.max(size.value.height - layout.value.height, 0); contentOffsetY.value = Math.max(-actualTotalPadding, Math.min(scroll.value, maxScroll)); } else { const maxScroll = Math.max(size.value.height - layout.value.height + actualTotalPadding, 0); contentOffsetY.value = Math.max(0, Math.min(scroll.value, maxScroll)); } return; } // Undo only the scroll displacement that was actually applied // (not the full padding, which includes the absorbed portion). // Use the stored absorption from the previous event so that // the unwind matches the shift that was originally applied. const prevScrollEffective = (0, _helpers.getScrollEffective)(padding.value, prevAbsorption.value); const relativeScroll = inverted ? scroll.value + prevScrollEffective : scroll.value - prevScrollEffective; padding.value = effective; prevAbsorption.value = minimumPaddingAbsorbed; if (!(0, _helpers.shouldShiftContent)(keyboardLiftBehavior, atEnd)) { // Preserve current scroll position so animated props // don't re-apply a stale contentOffset when padding changes contentOffsetY.value = scroll.value; return; } // When blankSpace fully absorbs the keyboard opening, preserve current scroll position // (only when keyboard is open — effective > 0 — not when closing) if (scrollEffective === 0 && minimumPaddingAbsorbed > 0 && effective > 0) { contentOffsetY.value = scroll.value; return; } contentOffsetY.value = (0, _helpers.computeIOSContentOffset)(relativeScroll, scrollEffective, size.value.height, layout.value.height, inverted, actualTotalPadding); }, onMove: () => { "worklet"; // iOS doesn't need per-frame updates (contentOffset handles it) }, onEnd: e => { "worklet"; if (freeze.value) { return; } const effective = (0, _helpers.getEffectiveHeight)(e.height, targetKeyboardHeight.value, offset); padding.value = effective; } }, [inverted, keyboardLiftBehavior, offset, extraContentPadding]); return { padding, currentHeight, contentOffsetY, scroll, layout, size, onLayout, onContentSizeChange }; } //# sourceMappingURL=index.ios.js.map