react-native-gesture-handler
Version:
Declarative API exposing native platform touch and gesture system to React Native
181 lines (179 loc) • 6.48 kB
JavaScript
;
import React, { useCallback, useRef } from 'react';
import { Platform } from 'react-native';
import GestureHandlerButton from '../../../components/GestureHandlerButton';
import { NativeDetector } from '../../detectors/NativeDetector';
import { useNativeGesture } from '../../hooks';
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;
var PointerState = /*#__PURE__*/function (PointerState) {
PointerState[PointerState["UNKNOWN"] = 0] = "UNKNOWN";
PointerState[PointerState["INSIDE"] = 1] = "INSIDE";
PointerState[PointerState["OUTSIDE"] = 2] = "OUTSIDE";
return PointerState;
}(PointerState || {}); // 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,
children,
disabled = false,
cancelOnLeave = true,
ref,
...rest
} = props;
const resolvedDurations = resolveAnimationDuration(animationDuration);
const resolvedDelayLongPress = sanitizeDuration(delayLongPress);
const shouldUseNativeRipple = isAndroid && androidRipple !== undefined;
const pointerState = useRef(PointerState.UNKNOWN);
const longPressDetected = useRef(false);
const longPressTimeout = useRef(undefined);
const wrappedLongPress = useCallback(() => {
longPressDetected.current = true;
onLongPress?.();
}, [onLongPress]);
const startLongPressTimer = useCallback(() => {
longPressDetected.current = false;
if (onLongPress && !longPressTimeout.current) {
longPressTimeout.current = setTimeout(wrappedLongPress, resolvedDelayLongPress);
}
}, [onLongPress, resolvedDelayLongPress, wrappedLongPress]);
const onBegin = useCallback(e => {
if (!e.pointerInside) {
pointerState.current = PointerState.OUTSIDE;
return;
}
onPressIn?.(e);
startLongPressTimer();
pointerState.current = PointerState.INSIDE;
}, [startLongPressTimer, onPressIn]);
const onActivate = useCallback(e => {
if (!e.pointerInside && longPressTimeout.current !== undefined) {
clearTimeout(longPressTimeout.current);
longPressTimeout.current = undefined;
}
}, []);
const onFinalize = useCallback(e => {
if (pointerState.current === PointerState.INSIDE) {
onPressOut?.(e);
}
if (!e.canceled && !longPressDetected.current && e.pointerInside) {
onPress?.(e);
}
pointerState.current = PointerState.UNKNOWN;
if (longPressTimeout.current !== undefined) {
clearTimeout(longPressTimeout.current);
longPressTimeout.current = undefined;
}
}, [onPressOut, onPress]);
const onUpdate = useCallback(e => {
if (pointerState.current === PointerState.UNKNOWN) {
return;
}
if (e.pointerInside) {
if (pointerState.current === PointerState.OUTSIDE) {
onPressIn?.(e);
}
pointerState.current = PointerState.INSIDE;
} else {
if (pointerState.current === PointerState.INSIDE) {
onPressOut?.(e);
if (longPressTimeout.current !== undefined) {
clearTimeout(longPressTimeout.current);
longPressTimeout.current = undefined;
}
}
pointerState.current = PointerState.OUTSIDE;
}
}, [onPressIn, onPressOut]);
const nativeGesture = useNativeGesture({
onBegin,
onActivate,
onFinalize,
onUpdate,
hitSlop: props.hitSlop,
testID: props.testID,
enabled: !disabled,
shouldCancelWhenOutside: cancelOnLeave,
disableReanimated: true,
shouldActivateOnStart: false,
disallowInterruption: true,
yieldsToContinuousGestures: true
});
const rippleProps = shouldUseNativeRipple ? {
rippleColor: androidRipple?.color,
rippleRadius: androidRipple?.radius,
borderless: androidRipple?.borderless,
foreground: androidRipple?.foreground
} : TRANSPARENT_RIPPLE;
return /*#__PURE__*/_jsx(NativeDetector, {
gesture: nativeGesture,
children: /*#__PURE__*/_jsx(GestureHandlerButton, {
...rest,
...rippleProps,
...resolvedDurations,
ref: ref ?? null,
enabled: !disabled,
defaultOpacity: defaultOpacity,
defaultUnderlayOpacity: defaultUnderlayOpacity,
activeUnderlayOpacity: activeUnderlayOpacity,
underlayColor: underlayColor,
longPressDuration: resolvedDelayLongPress,
children: children
})
});
};
//# sourceMappingURL=Touchable.js.map