UNPKG

react-native-gesture-handler

Version:

Declarative API exposing native platform touch and gesture system to React Native

133 lines (127 loc) 5.57 kB
"use strict"; import React, { useCallback, useEffect, useMemo, useRef } from 'react'; import { Keyboard, Platform, StyleSheet, TextInput, View } from 'react-native'; import { JSResponderContext, setKeyboardVisibility } from '../scrollViewInterop'; // The listeners are shared by every mounted ScrollView, so attach/detach is // reference-counted: we subscribe on the first interceptor and tear down only // once the last one unmounts. import { jsx as _jsx } from "react/jsx-runtime"; let keyboardTrackerRefCount = 0; let keyboardTrackerSubscriptions = []; function keyboardIsOpen(height) { return height != null && height > 0; } function subscribeToKeyboardVisibility() { keyboardTrackerRefCount++; if (keyboardTrackerRefCount > 1 || Keyboard?.addListener == null) { return; } const setVisible = event => { setKeyboardVisibility(keyboardIsOpen(event.endCoordinates?.height)); }; const setHidden = () => { setKeyboardVisibility(false); }; // Seed from the current keyboard metrics in case it is already open when the // first ScrollView mounts (mirrors ScrollView seeding from Keyboard.metrics()). setKeyboardVisibility(keyboardIsOpen(Keyboard.metrics?.()?.height)); keyboardTrackerSubscriptions = [Keyboard.addListener('keyboardDidShow', setVisible), Keyboard.addListener('keyboardWillShow', setVisible), Keyboard.addListener('keyboardDidHide', setHidden)]; } function unsubscribeFromKeyboardVisibility() { keyboardTrackerRefCount--; if (keyboardTrackerRefCount > 0) { return; } for (const subscription of keyboardTrackerSubscriptions) { subscription.remove(); } keyboardTrackerSubscriptions = []; setKeyboardVisibility(false); } // Mirrors ScrollView's `_keyboardIsDismissible` + `_softKeyboardIsDetached` // (react-native/Libraries/Components/ScrollView/ScrollView.js) using only // public API. function keyboardIsDismissible() { const currentlyFocusedInput = TextInput.State.currentlyFocusedInput(); if (currentlyFocusedInput == null) { return false; } const metrics = Keyboard.metrics?.(); const softKeyboardMayBeOpen = metrics != null || Platform.OS === 'android' && Number(Platform.Version) < 30; const softKeyboardIsDetached = metrics != null && metrics.height === 0; return softKeyboardMayBeOpen && !softKeyboardIsDetached; } // In 'handled' mode, consumes RNGH-marked responder events so handled taps // don't dismiss the keyboard (see the PR #4158 discussion), and reimplements // RN ScrollView's own 'handled' dismissal, which GH ScrollView turns off via // `disableScrollViewPanResponder`. The wrapper sits ABOVE the ScrollView so // its children stay untouched for `stickyHeaderIndices` (#4328). // https://github.com/software-mansion/react-native-gesture-handler/pull/4158#issuecomment-4431632964 export const ScrollViewResponderProvider = ({ children, keyboardShouldPersistTaps }) => { const isRNGHResponderEvent = useRef(false); const claimedForKeyboardDismissal = useRef(false); const contextValue = useMemo(() => ({ isRNGHResponderEvent, keyboardShouldPersistTaps }), [isRNGHResponderEvent, keyboardShouldPersistTaps]); useEffect(() => { subscribeToKeyboardVisibility(); return () => unsubscribeFromKeyboardVisibility(); }, []); const resetRNGHResponderEvent = useCallback(() => { isRNGHResponderEvent.current = false; return false; }, []); const handleStartShouldSetResponder = useCallback(event => { if (isRNGHResponderEvent.current) { // Claim marked events so outer keyboard-dismissing responders can't — // release does nothing and the keyboard stays open. isRNGHResponderEvent.current = false; claimedForKeyboardDismissal.current = false; return true; } // Unhandled tap — claim to dismiss the keyboard on release, like RN // ScrollView's 'handled' claim would. const shouldClaim = keyboardIsDismissible() && event.target !== TextInput.State.currentlyFocusedInput(); claimedForKeyboardDismissal.current = shouldClaim; return shouldClaim; }, []); const handleResponderRelease = useCallback(event => { if (!claimedForKeyboardDismissal.current) { return; } claimedForKeyboardDismissal.current = false; const currentlyFocusedInput = TextInput.State.currentlyFocusedInput(); if (currentlyFocusedInput != null && keyboardIsDismissible() && event.target !== currentlyFocusedInput) { TextInput.State.blurTextInput(currentlyFocusedInput); } }, []); // A native scroll taking over terminates the JS responder — mirror RN's // `_observedScrollSinceBecomingResponder` guard by skipping the dismissal. const handleResponderTerminate = useCallback(() => { claimedForKeyboardDismissal.current = false; }, []); const isHandledMode = keyboardShouldPersistTaps === 'handled'; return /*#__PURE__*/_jsx(JSResponderContext, { value: contextValue, children: /*#__PURE__*/_jsx(View, { collapsable: false, pointerEvents: "box-none", style: styles.logicalResponder, onStartShouldSetResponderCapture: isHandledMode ? resetRNGHResponderEvent : undefined, onStartShouldSetResponder: isHandledMode ? handleStartShouldSetResponder : undefined, onResponderRelease: isHandledMode ? handleResponderRelease : undefined, onResponderTerminate: isHandledMode ? handleResponderTerminate : undefined, children: children }) }); }; const styles = StyleSheet.create({ logicalResponder: { display: 'contents' } }); //# sourceMappingURL=ScrollViewResponderInterceptor.js.map