react-native-gesture-handler
Version:
Declarative API exposing native platform touch and gesture system to React Native
346 lines (331 loc) • 15.7 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 _reactNativeReanimated = _interopRequireWildcard(require("react-native-reanimated"));
var _reactNative = require("react-native");
var _utils = require("./utils");
var _jsxRuntime = require("react/jsx-runtime");
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); }
// Similarily to the DrawerLayout component this deserves to be put in a
// separate repo. Although, keeping it here for the time being will allow us to
// move faster and fix possible issues quicker
const DRAG_TOSS = 0.05;
var SwipeDirection = /*#__PURE__*/function (SwipeDirection) {
SwipeDirection["LEFT"] = "left";
SwipeDirection["RIGHT"] = "right";
return SwipeDirection;
}(SwipeDirection || {});
const Swipeable = /*#__PURE__*/(0, _react.forwardRef)(function Swipeable(props, ref) {
const defaultProps = {
friction: 1,
overshootFriction: 1,
dragOffset: 10,
enableTrackpadTwoFingerGesture: false
};
const {
leftThreshold,
rightThreshold,
enabled,
containerStyle,
childrenContainerStyle,
animationOptions,
overshootLeft,
overshootRight,
testID,
children,
enableTrackpadTwoFingerGesture = defaultProps.enableTrackpadTwoFingerGesture,
dragOffsetFromLeftEdge = defaultProps.dragOffset,
dragOffsetFromRightEdge = defaultProps.dragOffset,
friction = defaultProps.friction,
overshootFriction = defaultProps.overshootFriction,
onSwipeableOpenStartDrag,
onSwipeableCloseStartDrag,
onSwipeableWillOpen,
onSwipeableWillClose,
onSwipeableOpen,
onSwipeableClose,
renderLeftActions,
renderRightActions,
simultaneousWithExternalGesture,
requireExternalGestureToFail,
blocksExternalGesture,
hitSlop,
...remainingProps
} = props;
const relationProps = {
simultaneousWithExternalGesture,
requireExternalGestureToFail,
blocksExternalGesture
};
const rowState = (0, _reactNativeReanimated.useSharedValue)(0);
const userDrag = (0, _reactNativeReanimated.useSharedValue)(0);
const appliedTranslation = (0, _reactNativeReanimated.useSharedValue)(0);
const rowWidth = (0, _reactNativeReanimated.useSharedValue)(0);
const leftWidth = (0, _reactNativeReanimated.useSharedValue)(0);
const rightWidth = (0, _reactNativeReanimated.useSharedValue)(0);
const showLeftProgress = (0, _reactNativeReanimated.useSharedValue)(0);
const showRightProgress = (0, _reactNativeReanimated.useSharedValue)(0);
const updateAnimatedEvent = (0, _react.useCallback)(() => {
'worklet';
const shouldOvershootLeft = overshootLeft ?? leftWidth.value > 0;
const shouldOvershootRight = overshootRight ?? rightWidth.value > 0;
const startOffset = rowState.value === 1 ? leftWidth.value : rowState.value === -1 ? -rightWidth.value : 0;
const offsetDrag = userDrag.value / friction + startOffset;
appliedTranslation.value = (0, _reactNativeReanimated.interpolate)(offsetDrag, [-rightWidth.value - 1, -rightWidth.value, leftWidth.value, leftWidth.value + 1], [-rightWidth.value - (shouldOvershootRight ? 1 / overshootFriction : 0), -rightWidth.value, leftWidth.value, leftWidth.value + (shouldOvershootLeft ? 1 / overshootFriction : 0)]);
showLeftProgress.value = leftWidth.value > 0 ? (0, _reactNativeReanimated.interpolate)(appliedTranslation.value, [-1, 0, leftWidth.value], [0, 0, 1]) : 0;
showRightProgress.value = rightWidth.value > 0 ? (0, _reactNativeReanimated.interpolate)(appliedTranslation.value, [-rightWidth.value, 0, 1], [1, 0, 0]) : 0;
}, [appliedTranslation, friction, leftWidth, overshootFriction, rightWidth, rowState, showLeftProgress, showRightProgress, userDrag, overshootLeft, overshootRight]);
const dispatchImmediateEvents = (0, _react.useCallback)((fromValue, toValue) => {
'worklet';
if (onSwipeableWillOpen && toValue !== 0) {
(0, _reactNativeReanimated.runOnJS)(onSwipeableWillOpen)(toValue > 0 ? SwipeDirection.RIGHT : SwipeDirection.LEFT);
}
if (onSwipeableWillClose && toValue === 0) {
(0, _reactNativeReanimated.runOnJS)(onSwipeableWillClose)(fromValue > 0 ? SwipeDirection.LEFT : SwipeDirection.RIGHT);
}
}, [onSwipeableWillClose, onSwipeableWillOpen, rowState]);
const dispatchEndEvents = (0, _react.useCallback)((fromValue, toValue) => {
'worklet';
if (onSwipeableOpen && toValue !== 0) {
(0, _reactNativeReanimated.runOnJS)(onSwipeableOpen)(toValue > 0 ? SwipeDirection.RIGHT : SwipeDirection.LEFT);
}
if (onSwipeableClose && toValue === 0) {
(0, _reactNativeReanimated.runOnJS)(onSwipeableClose)(fromValue > 0 ? SwipeDirection.LEFT : SwipeDirection.RIGHT);
}
}, [onSwipeableClose, onSwipeableOpen]);
const animateRow = (0, _react.useCallback)((toValue, velocityX) => {
'worklet';
const translationSpringConfig = {
mass: 2,
damping: 1000,
stiffness: 700,
velocity: velocityX,
overshootClamping: true,
reduceMotion: _reactNativeReanimated.ReduceMotion.System,
...animationOptions
};
const isClosing = toValue === 0;
const moveToRight = isClosing ? rowState.value < 0 : toValue > 0;
const usedWidth = isClosing ? moveToRight ? rightWidth.value : leftWidth.value : moveToRight ? leftWidth.value : rightWidth.value;
const progressSpringConfig = {
...translationSpringConfig,
restDisplacementThreshold: 0.01,
restSpeedThreshold: 0.01,
velocity: velocityX && (0, _reactNativeReanimated.interpolate)(velocityX, [-usedWidth, usedWidth], [-1, 1])
};
const frozenRowState = rowState.value;
appliedTranslation.value = (0, _reactNativeReanimated.withSpring)(toValue, translationSpringConfig, isFinished => {
if (isFinished) {
dispatchEndEvents(frozenRowState, toValue);
}
});
const progressTarget = toValue === 0 ? 0 : 1 * Math.sign(toValue);
showLeftProgress.value = (0, _reactNativeReanimated.withSpring)(Math.max(progressTarget, 0), progressSpringConfig);
showRightProgress.value = (0, _reactNativeReanimated.withSpring)(Math.max(-progressTarget, 0), progressSpringConfig);
dispatchImmediateEvents(frozenRowState, toValue);
rowState.value = Math.sign(toValue);
}, [rowState, animationOptions, appliedTranslation, showLeftProgress, leftWidth, showRightProgress, rightWidth, dispatchImmediateEvents, dispatchEndEvents]);
const leftLayoutRef = (0, _reactNativeReanimated.useAnimatedRef)();
const leftWrapperLayoutRef = (0, _reactNativeReanimated.useAnimatedRef)();
const rightLayoutRef = (0, _reactNativeReanimated.useAnimatedRef)();
const updateElementWidths = (0, _react.useCallback)(() => {
'worklet';
const leftLayout = (0, _reactNativeReanimated.measure)(leftLayoutRef);
const leftWrapperLayout = (0, _reactNativeReanimated.measure)(leftWrapperLayoutRef);
const rightLayout = (0, _reactNativeReanimated.measure)(rightLayoutRef);
leftWidth.value = (leftLayout?.pageX ?? 0) - (leftWrapperLayout?.pageX ?? 0);
rightWidth.value = rowWidth.value - (rightLayout?.pageX ?? rowWidth.value) + (leftWrapperLayout?.pageX ?? 0);
}, [leftLayoutRef, leftWrapperLayoutRef, rightLayoutRef, leftWidth, rightWidth, rowWidth]);
const swipeableMethods = (0, _react.useMemo)(() => ({
close() {
'worklet';
if (_WORKLET) {
animateRow(0);
return;
}
(0, _reactNativeReanimated.runOnUI)(() => {
animateRow(0);
})();
},
openLeft() {
'worklet';
if (_WORKLET) {
updateElementWidths();
animateRow(leftWidth.value);
return;
}
(0, _reactNativeReanimated.runOnUI)(() => {
updateElementWidths();
animateRow(leftWidth.value);
})();
},
openRight() {
'worklet';
if (_WORKLET) {
updateElementWidths();
animateRow(-rightWidth.value);
return;
}
(0, _reactNativeReanimated.runOnUI)(() => {
updateElementWidths();
animateRow(-rightWidth.value);
})();
},
reset() {
'worklet';
userDrag.value = 0;
showLeftProgress.value = 0;
appliedTranslation.value = 0;
rowState.value = 0;
}
}), [animateRow, updateElementWidths, leftWidth, rightWidth, userDrag, showLeftProgress, appliedTranslation, rowState]);
const onRowLayout = (0, _react.useCallback)(({
nativeEvent
}) => {
rowWidth.value = nativeEvent.layout.width;
}, [rowWidth]);
// As stated in `Dimensions.get` docstring, this function should be called on every render
// since dimensions may change (e.g. orientation change)
const leftActionAnimation = (0, _reactNativeReanimated.useAnimatedStyle)(() => {
return {
opacity: showLeftProgress.value === 0 ? 0 : 1
};
});
const leftElement = (0, _react.useCallback)(() => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNativeReanimated.default.View, {
ref: leftWrapperLayoutRef,
style: [styles.leftActions, leftActionAnimation],
children: [renderLeftActions?.(showLeftProgress, appliedTranslation, swipeableMethods), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeReanimated.default.View, {
ref: leftLayoutRef
})]
}), [appliedTranslation, leftActionAnimation, leftLayoutRef, leftWrapperLayoutRef, renderLeftActions, showLeftProgress, swipeableMethods]);
const rightActionAnimation = (0, _reactNativeReanimated.useAnimatedStyle)(() => {
return {
opacity: showRightProgress.value === 0 ? 0 : 1
};
});
const rightElement = (0, _react.useCallback)(() => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNativeReanimated.default.View, {
style: [styles.rightActions, rightActionAnimation],
children: [renderRightActions?.(showRightProgress, appliedTranslation, swipeableMethods), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeReanimated.default.View, {
ref: rightLayoutRef
})]
}), [appliedTranslation, renderRightActions, rightActionAnimation, rightLayoutRef, showRightProgress, swipeableMethods]);
const handleRelease = (0, _react.useCallback)(event => {
'worklet';
const {
velocityX
} = event;
userDrag.value = event.translationX;
const leftThresholdProp = leftThreshold ?? leftWidth.value / 2;
const rightThresholdProp = rightThreshold ?? rightWidth.value / 2;
const translationX = (userDrag.value + DRAG_TOSS * velocityX) / friction;
let toValue = 0;
if (rowState.value === 0) {
if (translationX > leftThresholdProp) {
toValue = leftWidth.value;
} else if (translationX < -rightThresholdProp) {
toValue = -rightWidth.value;
}
} else if (rowState.value === 1) {
// Swiped to left
if (translationX > -leftThresholdProp) {
toValue = leftWidth.value;
}
} else {
// Swiped to right
if (translationX < rightThresholdProp) {
toValue = -rightWidth.value;
}
}
animateRow(toValue, velocityX / friction);
}, [animateRow, friction, leftThreshold, leftWidth, rightThreshold, rightWidth, rowState, userDrag]);
const close = (0, _react.useCallback)(() => {
'worklet';
animateRow(0);
}, [animateRow]);
const dragStarted = (0, _reactNativeReanimated.useSharedValue)(false);
const tapGesture = (0, _react.useMemo)(() => {
const tap = _gestureObjects.GestureObjects.Tap().shouldCancelWhenOutside(true).onStart(() => {
if (rowState.value !== 0) {
close();
}
});
Object.entries(relationProps).forEach(([relationName, relation]) => {
(0, _utils.applyRelationProp)(tap, relationName, relation);
});
return tap;
}, [close, rowState, simultaneousWithExternalGesture]);
const panGesture = (0, _react.useMemo)(() => {
const pan = _gestureObjects.GestureObjects.Pan().enabled(enabled !== false).enableTrackpadTwoFingerGesture(enableTrackpadTwoFingerGesture).activeOffsetX([-dragOffsetFromRightEdge, dragOffsetFromLeftEdge]).onStart(updateElementWidths).onUpdate(event => {
userDrag.value = event.translationX;
const direction = rowState.value === -1 ? SwipeDirection.RIGHT : rowState.value === 1 ? SwipeDirection.LEFT : event.translationX > 0 ? SwipeDirection.RIGHT : SwipeDirection.LEFT;
if (!dragStarted.value) {
dragStarted.value = true;
if (rowState.value === 0 && onSwipeableOpenStartDrag) {
(0, _reactNativeReanimated.runOnJS)(onSwipeableOpenStartDrag)(direction);
} else if (onSwipeableCloseStartDrag) {
(0, _reactNativeReanimated.runOnJS)(onSwipeableCloseStartDrag)(direction);
}
}
updateAnimatedEvent();
}).onEnd(event => {
handleRelease(event);
}).onFinalize(() => {
dragStarted.value = false;
});
Object.entries(relationProps).forEach(([relationName, relation]) => {
(0, _utils.applyRelationProp)(pan, relationName, relation);
});
return pan;
}, [dragOffsetFromLeftEdge, dragOffsetFromRightEdge, dragStarted, enableTrackpadTwoFingerGesture, enabled, handleRelease, onSwipeableCloseStartDrag, onSwipeableOpenStartDrag, rowState, updateAnimatedEvent, updateElementWidths, userDrag, simultaneousWithExternalGesture]);
(0, _react.useImperativeHandle)(ref, () => swipeableMethods, [swipeableMethods]);
const animatedStyle = (0, _reactNativeReanimated.useAnimatedStyle)(() => ({
transform: [{
translateX: appliedTranslation.value
}],
pointerEvents: rowState.value === 0 ? 'auto' : 'box-only'
}), [appliedTranslation, rowState]);
const swipeableComponent = /*#__PURE__*/(0, _jsxRuntime.jsx)(_GestureDetector.GestureDetector, {
gesture: panGesture,
touchAction: "pan-y",
children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNativeReanimated.default.View, {
...remainingProps,
onLayout: onRowLayout,
hitSlop: hitSlop ?? undefined,
style: [styles.container, containerStyle],
children: [leftElement(), rightElement(), /*#__PURE__*/(0, _jsxRuntime.jsx)(_GestureDetector.GestureDetector, {
gesture: tapGesture,
touchAction: "pan-y",
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeReanimated.default.View, {
style: [animatedStyle, childrenContainerStyle],
children: children
})
})]
})
});
return testID ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
testID: testID,
children: swipeableComponent
}) : swipeableComponent;
});
var _default = exports.default = Swipeable;
const styles = _reactNative.StyleSheet.create({
container: {
overflow: 'hidden'
},
leftActions: {
..._reactNative.StyleSheet.absoluteFillObject,
flexDirection: _reactNative.I18nManager.isRTL ? 'row-reverse' : 'row',
overflow: 'hidden'
},
rightActions: {
..._reactNative.StyleSheet.absoluteFillObject,
flexDirection: _reactNative.I18nManager.isRTL ? 'row' : 'row-reverse',
overflow: 'hidden'
}
});
//# sourceMappingURL=ReanimatedSwipeable.js.map
;