UNPKG

react-native-gesture-handler

Version:

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

178 lines (172 loc) 6.89 kB
"use strict"; import React, { use, useCallback, useMemo, useRef, useState } from 'react'; import { Platform } from 'react-native'; import GestureHandlerButton from '../../../components/GestureHandlerButton'; import { getTVProps } from '../../../components/utils'; import { getNextHandlerTag } from '../../../handlers/getNextHandlerTag'; import { isKeyboardDismissingTap, JSResponderContext, updateResponderEventValue } from '../../scrollViewInterop'; import { jsx as _jsx } from "react/jsx-runtime"; const isAndroid = Platform.OS === 'android'; const TRANSPARENT_RIPPLE = { rippleColor: 'transparent' }; const DEFAULT_IN_DURATION_MS = 50; const DEFAULT_OUT_DURATION_MS = 100; // Clamp user-supplied durations to finite, non-negative milliseconds. // Negative, NaN, or Infinity values would produce invalid CSS transitions // on web and negative setTimeout delays in branch 3 of the press-out path. function sanitizeDuration(value) { return Number.isFinite(value) && value >= 0 ? value : 0; } function resolveAnimationDuration(value) { if (value === undefined) { return { tapAnimationInDuration: DEFAULT_IN_DURATION_MS, tapAnimationOutDuration: DEFAULT_OUT_DURATION_MS, longPressAnimationOutDuration: DEFAULT_OUT_DURATION_MS, hoverAnimationInDuration: DEFAULT_IN_DURATION_MS, hoverAnimationOutDuration: DEFAULT_OUT_DURATION_MS }; } if (typeof value === 'number') { const sanitized = sanitizeDuration(value); return { tapAnimationInDuration: sanitized, tapAnimationOutDuration: sanitized, longPressAnimationOutDuration: sanitized, hoverAnimationInDuration: sanitized, hoverAnimationOutDuration: sanitized }; } // The union guarantees variant 2 supplies top-level `in`/`out`, variant 3 // supplies both category objects — so per-category fallback to base is // always defined for well-typed input; the 0 fallbacks here are unreachable. const baseIn = 'in' in value ? value.in : 0; const baseOut = 'out' in value ? value.out : 0; const tapOut = value.tap?.out ?? baseOut; return { tapAnimationInDuration: sanitizeDuration(value.tap?.in ?? baseIn), tapAnimationOutDuration: sanitizeDuration(tapOut), longPressAnimationOutDuration: sanitizeDuration(value.longPress?.out ?? tapOut), hoverAnimationInDuration: sanitizeDuration(value.hover?.in ?? baseIn), hoverAnimationOutDuration: sanitizeDuration(value.hover?.out ?? baseOut) }; } export const Touchable = props => { const { underlayColor = 'transparent', defaultUnderlayOpacity = 0, activeUnderlayOpacity = 0.105, defaultOpacity = 1, animationDuration, androidRipple, delayLongPress = 600, onLongPress, onPress, onPressIn, onPressOut, onHoverIn, onHoverOut, children, disabled = false, cancelOnLeave = true, ref, ...rest } = props; const [handlerTag] = useState(() => getNextHandlerTag()); const resolvedDurations = resolveAnimationDuration(animationDuration); const resolvedDelayLongPress = sanitizeDuration(delayLongPress); const shouldUseNativeRipple = isAndroid && androidRipple !== undefined; const jsResponderContext = use(JSResponderContext); const dropKeyboardTapRef = useRef(null); // The button handles presses natively, without a NativeDetector, so it has // to mark its responder events as RNGH-handled itself — otherwise a tap on // it looks unhandled to an enclosing ScrollView in // keyboardShouldPersistTaps='handled' mode and dismisses the keyboard. const handleStartShouldSetResponderCapture = useCallback(() => { if (!disabled) { updateResponderEventValue(jsResponderContext, true); } return false; }, [disabled, jsResponderContext]); const internalOnPress = useCallback(e => { if (dropKeyboardTapRef.current) { return; } onPress?.(e.nativeEvent); }, [onPress]); const internalOnPressIn = useCallback(e => { // PressIn opens every press sequence; capture the verdict once per // sequence so a re-entry PressIn (with cancelOnLeave={false}) doesn't // overwrite it after the keyboard is already dismissed. dropKeyboardTapRef.current ??= isKeyboardDismissingTap(jsResponderContext); if (dropKeyboardTapRef.current) { return; } onPressIn?.(e.nativeEvent); }, [jsResponderContext, onPressIn]); const internalOnPressOut = useCallback(e => { if (dropKeyboardTapRef.current) { return; } onPressOut?.(e.nativeEvent); }, [onPressOut]); const internalOnLongPress = useCallback(e => { if (dropKeyboardTapRef.current) { return; } onLongPress?.(e.nativeEvent); }, [onLongPress]); // Left undefined when the corresponding prop is absent so web can skip // building a hover payload nobody consumes — it costs a synchronous layout // read per pointer enter/leave. The native platforms emit either way. const internalOnHoverIn = useMemo(() => onHoverIn ? e => onHoverIn(e.nativeEvent) : undefined, [onHoverIn]); const internalOnHoverOut = useMemo(() => onHoverOut ? e => onHoverOut(e.nativeEvent) : undefined, [onHoverOut]); // InteractionFinished is dispatched after the terminal PressOut/Press // events, so resetting synchronously here is safe. const internalOnInteractionFinished = useCallback(() => { dropKeyboardTapRef.current = null; }, []); const rippleProps = shouldUseNativeRipple ? { rippleColor: androidRipple?.color, rippleRadius: androidRipple?.radius, borderless: androidRipple?.borderless, foreground: androidRipple?.foreground } : TRANSPARENT_RIPPLE; const tvProps = getTVProps(rest); const hitSlop = typeof props.hitSlop === 'number' ? { top: props.hitSlop, left: props.hitSlop, bottom: props.hitSlop, right: props.hitSlop } : props.hitSlop; return /*#__PURE__*/_jsx(GestureHandlerButton, { ...rest, ...tvProps, ...rippleProps, ...resolvedDurations, ref: ref ?? null, enabled: !disabled, onStartShouldSetResponderCapture: handleStartShouldSetResponderCapture, moduleId: globalThis._RNGH_MODULE_ID, handlerTag: handlerTag, cancelOnLeave: cancelOnLeave, gestureTestID: props.testID, gestureHitSlop: hitSlop, defaultOpacity: defaultOpacity, defaultUnderlayOpacity: defaultUnderlayOpacity, activeUnderlayOpacity: activeUnderlayOpacity, underlayColor: underlayColor, longPressDuration: resolvedDelayLongPress, hasLongPressHandler: onLongPress !== undefined, onButtonPress: internalOnPress, onButtonPressIn: internalOnPressIn, onButtonPressOut: internalOnPressOut, onButtonLongPress: internalOnLongPress, onButtonHoverIn: internalOnHoverIn, onButtonHoverOut: internalOnHoverOut, onButtonInteractionFinished: internalOnInteractionFinished, children: children }); }; //# sourceMappingURL=Touchable.js.map