react-native-gesture-handler
Version:
Declarative API exposing native platform touch and gesture system to React Native
275 lines (271 loc) • 12 kB
JavaScript
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _react = _interopRequireWildcard(require("react"));
var _gestureObjects = require("../../handlers/gestures/gestureObjects");
var _GestureDetector = require("../../handlers/gestures/GestureDetector");
var _reactNative = require("react-native");
var _GestureHandlerButton = _interopRequireDefault(require("../GestureHandlerButton"));
var _utils = require("./utils");
var _PressabilityDebugView = require("../../handlers/PressabilityDebugView");
var _utils2 = require("../../utils");
var _utils3 = require("../utils");
var _stateDefinitions = require("./stateDefinitions");
var _jsxRuntime = require("react/jsx-runtime");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
const DEFAULT_LONG_PRESS_DURATION = 500;
const IS_TEST_ENV = (0, _utils2.isTestEnv)();
let IS_FABRIC = null;
const Pressable = props => {
const {
ref,
testOnly_pressed,
hitSlop,
pressRetentionOffset,
delayHoverIn,
delayHoverOut,
delayLongPress,
unstable_pressDelay,
onHoverIn,
onHoverOut,
onPress,
onPressIn,
onPressOut,
onLongPress,
style,
children,
android_disableSound,
android_ripple,
disabled,
accessible,
simultaneousWithExternalGesture,
requireExternalGestureToFail,
blocksExternalGesture,
dimensionsAfterResize,
...remainingProps
} = props;
const relationProps = {
simultaneousWithExternalGesture,
requireExternalGestureToFail,
blocksExternalGesture
};
// used only if `ref` is undefined
const fallbackRef = (0, _react.useRef)(null);
const [pressedState, setPressedState] = (0, _react.useState)(testOnly_pressed ?? false);
const longPressTimeoutRef = (0, _react.useRef)(null);
const pressDelayTimeoutRef = (0, _react.useRef)(null);
const isOnPressAllowed = (0, _react.useRef)(true);
const isCurrentlyPressed = (0, _react.useRef)(false);
const dimensions = (0, _react.useRef)({
width: 0,
height: 0
});
const normalizedHitSlop = (0, _react.useMemo)(() => typeof hitSlop === 'number' ? (0, _utils.numberAsInset)(hitSlop) : hitSlop ?? {}, [hitSlop]);
const normalizedPressRetentionOffset = (0, _react.useMemo)(() => typeof pressRetentionOffset === 'number' ? (0, _utils.numberAsInset)(pressRetentionOffset) : pressRetentionOffset ?? {}, [pressRetentionOffset]);
const appliedHitSlop = (0, _utils.addInsets)(normalizedHitSlop, normalizedPressRetentionOffset);
(0, _react.useLayoutEffect)(() => {
if (dimensionsAfterResize) {
dimensions.current = dimensionsAfterResize;
} else {
requestAnimationFrame(() => {
(ref ?? fallbackRef).current?.measure((_x, _y, width, height) => {
dimensions.current = {
width,
height
};
});
});
}
}, [dimensionsAfterResize, ref]);
const cancelLongPress = (0, _react.useCallback)(() => {
if (longPressTimeoutRef.current) {
clearTimeout(longPressTimeoutRef.current);
longPressTimeoutRef.current = null;
isOnPressAllowed.current = true;
}
}, []);
const cancelDelayedPress = (0, _react.useCallback)(() => {
if (pressDelayTimeoutRef.current) {
clearTimeout(pressDelayTimeoutRef.current);
pressDelayTimeoutRef.current = null;
}
}, []);
const startLongPress = (0, _react.useCallback)(event => {
if (onLongPress) {
cancelLongPress();
longPressTimeoutRef.current = setTimeout(() => {
isOnPressAllowed.current = false;
onLongPress(event);
}, delayLongPress ?? DEFAULT_LONG_PRESS_DURATION);
}
}, [onLongPress, cancelLongPress, delayLongPress]);
const innerHandlePressIn = (0, _react.useCallback)(event => {
onPressIn?.(event);
startLongPress(event);
setPressedState(true);
if (pressDelayTimeoutRef.current) {
clearTimeout(pressDelayTimeoutRef.current);
pressDelayTimeoutRef.current = null;
}
}, [onPressIn, startLongPress]);
const handleFinalize = (0, _react.useCallback)(() => {
isCurrentlyPressed.current = false;
cancelLongPress();
cancelDelayedPress();
setPressedState(false);
}, [cancelDelayedPress, cancelLongPress]);
const handlePressIn = (0, _react.useCallback)(event => {
if (!(0, _utils.isTouchWithinInset)(dimensions.current, normalizedHitSlop, event.nativeEvent.changedTouches.at(-1))) {
// Ignoring pressIn within pressRetentionOffset
return;
}
isCurrentlyPressed.current = true;
if (unstable_pressDelay) {
pressDelayTimeoutRef.current = setTimeout(() => {
innerHandlePressIn(event);
}, unstable_pressDelay);
} else {
innerHandlePressIn(event);
}
}, [innerHandlePressIn, normalizedHitSlop, unstable_pressDelay]);
const handlePressOut = (0, _react.useCallback)((event, success = true) => {
if (!isCurrentlyPressed.current) {
// Some prop configurations may lead to handlePressOut being called mutliple times.
return;
}
isCurrentlyPressed.current = false;
if (pressDelayTimeoutRef.current) {
innerHandlePressIn(event);
}
onPressOut?.(event);
if (isOnPressAllowed.current && success) {
onPress?.(event);
}
handleFinalize();
}, [handleFinalize, innerHandlePressIn, onPress, onPressOut]);
const stateMachine = (0, _react.useMemo)(() => (0, _stateDefinitions.getConfiguredStateMachine)(handlePressIn, handlePressOut), [handlePressIn, handlePressOut]);
const hoverInTimeout = (0, _react.useRef)(null);
const hoverOutTimeout = (0, _react.useRef)(null);
const hoverGesture = (0, _react.useMemo)(() => _gestureObjects.GestureObjects.Hover().manualActivation(true) // Prevents Hover blocking Gesture.Native() on web
.cancelsTouchesInView(false).onBegin(event => {
if (hoverOutTimeout.current) {
clearTimeout(hoverOutTimeout.current);
}
if (delayHoverIn) {
hoverInTimeout.current = setTimeout(() => onHoverIn?.((0, _utils.gestureToPressableEvent)(event)), delayHoverIn);
return;
}
onHoverIn?.((0, _utils.gestureToPressableEvent)(event));
}).onFinalize(event => {
if (hoverInTimeout.current) {
clearTimeout(hoverInTimeout.current);
}
if (delayHoverOut) {
hoverOutTimeout.current = setTimeout(() => onHoverOut?.((0, _utils.gestureToPressableEvent)(event)), delayHoverOut);
return;
}
onHoverOut?.((0, _utils.gestureToPressableEvent)(event));
}), [delayHoverIn, delayHoverOut, onHoverIn, onHoverOut]);
const pressAndTouchGesture = (0, _react.useMemo)(() => _gestureObjects.GestureObjects.LongPress().minDuration(_utils2.INT32_MAX) // Stops long press from blocking Gesture.Native()
.maxDistance(_utils2.INT32_MAX) // Stops long press from cancelling on touch move
.cancelsTouchesInView(false).onTouchesDown(event => {
const pressableEvent = (0, _utils.gestureTouchToPressableEvent)(event);
stateMachine.handleEvent(_stateDefinitions.StateMachineEvent.LONG_PRESS_TOUCHES_DOWN, pressableEvent);
}).onTouchesUp(() => {
if (_reactNative.Platform.OS === 'android') {
// Prevents potential soft-locks
stateMachine.reset();
handleFinalize();
}
}).onTouchesCancelled(event => {
const pressableEvent = (0, _utils.gestureTouchToPressableEvent)(event);
stateMachine.reset();
handlePressOut(pressableEvent, false);
}).onFinalize(() => {
if (_reactNative.Platform.OS === 'web') {
stateMachine.handleEvent(_stateDefinitions.StateMachineEvent.FINALIZE);
handleFinalize();
}
}), [stateMachine, handleFinalize, handlePressOut]);
// RNButton is placed inside ButtonGesture to enable Android's ripple and to capture non-propagating events
const buttonGesture = (0, _react.useMemo)(() => _gestureObjects.GestureObjects.Native().onTouchesCancelled(event => {
if (_reactNative.Platform.OS !== 'macos' && _reactNative.Platform.OS !== 'web') {
// On MacOS cancel occurs in middle of gesture
// On Web cancel occurs on mouse move, which is unwanted
const pressableEvent = (0, _utils.gestureTouchToPressableEvent)(event);
stateMachine.reset();
handlePressOut(pressableEvent, false);
}
}).onBegin(() => {
stateMachine.handleEvent(_stateDefinitions.StateMachineEvent.NATIVE_BEGIN);
}).onStart(() => {
if (_reactNative.Platform.OS !== 'android') {
// Gesture.Native().onStart() is broken with Android + hitSlop
stateMachine.handleEvent(_stateDefinitions.StateMachineEvent.NATIVE_START);
}
}).onFinalize(() => {
if (_reactNative.Platform.OS !== 'web') {
// On Web we use LongPress().onFinalize() instead of Native().onFinalize(),
// as Native cancels on mouse move, and LongPress does not.
stateMachine.handleEvent(_stateDefinitions.StateMachineEvent.FINALIZE);
handleFinalize();
}
}), [stateMachine, handlePressOut, handleFinalize]);
const isPressableEnabled = disabled !== true;
const gestures = [buttonGesture, pressAndTouchGesture, hoverGesture];
for (const gesture of gestures) {
gesture.enabled(isPressableEnabled);
gesture.runOnJS(true);
gesture.hitSlop(appliedHitSlop);
gesture.shouldCancelWhenOutside(_reactNative.Platform.OS !== 'web');
Object.entries(relationProps).forEach(([relationName, relation]) => {
(0, _utils3.applyRelationProp)(gesture, relationName, relation);
});
}
const gesture = _gestureObjects.GestureObjects.Simultaneous(...gestures);
// `cursor: 'pointer'` on `RNButton` crashes iOS
const pointerStyle = _reactNative.Platform.OS === 'web' ? {
cursor: 'pointer'
} : {};
const styleProp = typeof style === 'function' ? style({
pressed: pressedState
}) : style;
const childrenProp = typeof children === 'function' ? children({
pressed: pressedState
}) : children;
const rippleColor = (0, _react.useMemo)(() => {
if (IS_FABRIC === null) {
IS_FABRIC = (0, _utils2.isFabric)();
}
const defaultRippleColor = android_ripple ? undefined : 'transparent';
const unprocessedRippleColor = android_ripple?.color ?? defaultRippleColor;
return IS_FABRIC ? unprocessedRippleColor : (0, _reactNative.processColor)(unprocessedRippleColor);
}, [android_ripple]);
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_GestureDetector.GestureDetector, {
gesture: gesture,
children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_GestureHandlerButton.default, {
...remainingProps,
ref: ref ?? fallbackRef,
accessible: accessible !== false,
hitSlop: appliedHitSlop,
enabled: isPressableEnabled,
touchSoundDisabled: android_disableSound ?? undefined,
rippleColor: rippleColor,
rippleRadius: android_ripple?.radius ?? undefined,
style: [pointerStyle, styleProp],
testOnly_onPress: IS_TEST_ENV ? onPress : undefined,
testOnly_onPressIn: IS_TEST_ENV ? onPressIn : undefined,
testOnly_onPressOut: IS_TEST_ENV ? onPressOut : undefined,
testOnly_onLongPress: IS_TEST_ENV ? onLongPress : undefined,
children: [childrenProp, __DEV__ ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_PressabilityDebugView.PressabilityDebugView, {
color: "red",
hitSlop: normalizedHitSlop
}) : null]
})
});
};
var _default = exports.default = Pressable;
//# sourceMappingURL=Pressable.js.map
;