react-native-gesture-handler
Version:
Declarative API exposing native platform touch and gesture system to React Native
389 lines (337 loc) • 17.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = exports.DrawerKeyboardDismissMode = exports.DrawerLockMode = exports.DrawerType = exports.DrawerState = exports.DrawerPosition = void 0;
var _react = _interopRequireWildcard(require("react"));
var _reactNative = require("react-native");
var _reactNativeReanimated = _interopRequireWildcard(require("react-native-reanimated"));
var _gestureObjects = require("../handlers/gestures/gestureObjects");
var _GestureDetector = require("../handlers/gestures/GestureDetector");
var _gestureHandlerCommon = require("../handlers/gestureHandlerCommon");
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
// This component is based on RN's DrawerLayoutAndroid API
// It's cross-compatible with all platforms despite
// `DrawerLayoutAndroid` only being available on android
const DRAG_TOSS = 0.05;
let DrawerPosition;
exports.DrawerPosition = DrawerPosition;
(function (DrawerPosition) {
DrawerPosition[DrawerPosition["LEFT"] = 0] = "LEFT";
DrawerPosition[DrawerPosition["RIGHT"] = 1] = "RIGHT";
})(DrawerPosition || (exports.DrawerPosition = DrawerPosition = {}));
let DrawerState;
exports.DrawerState = DrawerState;
(function (DrawerState) {
DrawerState[DrawerState["IDLE"] = 0] = "IDLE";
DrawerState[DrawerState["DRAGGING"] = 1] = "DRAGGING";
DrawerState[DrawerState["SETTLING"] = 2] = "SETTLING";
})(DrawerState || (exports.DrawerState = DrawerState = {}));
let DrawerType;
exports.DrawerType = DrawerType;
(function (DrawerType) {
DrawerType[DrawerType["FRONT"] = 0] = "FRONT";
DrawerType[DrawerType["BACK"] = 1] = "BACK";
DrawerType[DrawerType["SLIDE"] = 2] = "SLIDE";
})(DrawerType || (exports.DrawerType = DrawerType = {}));
let DrawerLockMode;
exports.DrawerLockMode = DrawerLockMode;
(function (DrawerLockMode) {
DrawerLockMode[DrawerLockMode["UNLOCKED"] = 0] = "UNLOCKED";
DrawerLockMode[DrawerLockMode["LOCKED_CLOSED"] = 1] = "LOCKED_CLOSED";
DrawerLockMode[DrawerLockMode["LOCKED_OPEN"] = 2] = "LOCKED_OPEN";
})(DrawerLockMode || (exports.DrawerLockMode = DrawerLockMode = {}));
let DrawerKeyboardDismissMode;
exports.DrawerKeyboardDismissMode = DrawerKeyboardDismissMode;
(function (DrawerKeyboardDismissMode) {
DrawerKeyboardDismissMode[DrawerKeyboardDismissMode["NONE"] = 0] = "NONE";
DrawerKeyboardDismissMode[DrawerKeyboardDismissMode["ON_DRAG"] = 1] = "ON_DRAG";
})(DrawerKeyboardDismissMode || (exports.DrawerKeyboardDismissMode = DrawerKeyboardDismissMode = {}));
const defaultProps = {
drawerWidth: 200,
drawerPosition: DrawerPosition.LEFT,
drawerType: DrawerType.FRONT,
edgeWidth: 20,
minSwipeDistance: 3,
overlayColor: 'rgba(0, 0, 0, 0.7)',
drawerLockMode: DrawerLockMode.UNLOCKED,
enableTrackpadTwoFingerGesture: false,
activeCursor: 'auto',
mouseButton: _gestureHandlerCommon.MouseButton.LEFT,
statusBarAnimation: 'slide'
};
const DrawerLayout = /*#__PURE__*/(0, _react.forwardRef)(function DrawerLayout(props, ref) {
const [containerWidth, setContainerWidth] = (0, _react.useState)(0);
const [drawerState, setDrawerState] = (0, _react.useState)(DrawerState.IDLE);
const [drawerOpened, setDrawerOpened] = (0, _react.useState)(false);
const {
drawerPosition = defaultProps.drawerPosition,
drawerWidth = defaultProps.drawerWidth,
drawerType = defaultProps.drawerType,
drawerBackgroundColor,
drawerContainerStyle,
contentContainerStyle,
minSwipeDistance = defaultProps.minSwipeDistance,
edgeWidth = defaultProps.edgeWidth,
drawerLockMode = defaultProps.drawerLockMode,
overlayColor = defaultProps.overlayColor,
enableTrackpadTwoFingerGesture = defaultProps.enableTrackpadTwoFingerGesture,
activeCursor = defaultProps.activeCursor,
mouseButton = defaultProps.mouseButton,
statusBarAnimation = defaultProps.statusBarAnimation,
hideStatusBar,
keyboardDismissMode,
userSelect,
enableContextMenu,
renderNavigationView,
onDrawerSlide,
onDrawerClose,
onDrawerOpen,
onDrawerStateChanged
} = props;
const isFromLeft = drawerPosition === DrawerPosition.LEFT;
const sideCorrection = isFromLeft ? 1 : -1; // While closing the drawer when user starts gesture in the greyed out part of the window,
// we want the drawer to follow only once the finger reaches the edge of the drawer.
// See the diagram for reference. * = starting finger position, < = current finger position
// 1) +---------------+ 2) +---------------+ 3) +---------------+ 4) +---------------+
// |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........|
// |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........|
// |XXXXXXXX|..<*..| |XXXXXXXX|.<-*..| |XXXXXXXX|<--*..| |XXXXX|<-----*..|
// |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........|
// |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........|
// +---------------+ +---------------+ +---------------+ +---------------+
const openValue = (0, _reactNativeReanimated.useSharedValue)(0);
(0, _reactNativeReanimated.useDerivedValue)(() => {
onDrawerSlide && (0, _reactNativeReanimated.runOnJS)(onDrawerSlide)(openValue.value);
}, []);
const isDrawerOpen = (0, _reactNativeReanimated.useSharedValue)(false);
const handleContainerLayout = ({
nativeEvent
}) => {
setContainerWidth(nativeEvent.layout.width);
};
const emitStateChanged = (0, _react.useCallback)((newState, drawerWillShow) => {
'worklet';
var _runOnJS;
onDrawerStateChanged && ((_runOnJS = (0, _reactNativeReanimated.runOnJS)(onDrawerStateChanged)) === null || _runOnJS === void 0 ? void 0 : _runOnJS(newState, drawerWillShow));
}, [onDrawerStateChanged]);
const drawerAnimatedProps = (0, _reactNativeReanimated.useAnimatedProps)(() => ({
accessibilityViewIsModal: isDrawerOpen.value
}));
const overlayAnimatedProps = (0, _reactNativeReanimated.useAnimatedProps)(() => ({
pointerEvents: isDrawerOpen.value ? 'auto' : 'none'
})); // While the drawer is hidden, it's hitSlop overflows onto the main view by edgeWidth
// This way it can be swiped open even when it's hidden
const [edgeHitSlop, setEdgeHitSlop] = (0, _react.useState)(isFromLeft ? {
left: 0,
width: edgeWidth
} : {
right: 0,
width: edgeWidth
}); // gestureOrientation is 1 if the gesture is expected to move from left to right and -1 otherwise
const gestureOrientation = (0, _react.useMemo)(() => sideCorrection * (drawerOpened ? -1 : 1), [sideCorrection, drawerOpened]);
(0, _react.useEffect)(() => {
setEdgeHitSlop(isFromLeft ? {
left: 0,
width: edgeWidth
} : {
right: 0,
width: edgeWidth
});
}, [isFromLeft, edgeWidth]);
const animateDrawer = (0, _react.useCallback)((toValue, initialVelocity, animationSpeed) => {
'worklet';
const willShow = toValue !== 0;
isDrawerOpen.value = willShow;
emitStateChanged(DrawerState.SETTLING, willShow);
(0, _reactNativeReanimated.runOnJS)(setDrawerState)(DrawerState.SETTLING);
if (hideStatusBar) {
(0, _reactNativeReanimated.runOnJS)(_reactNative.StatusBar.setHidden)(willShow, statusBarAnimation);
}
const normalizedToValue = (0, _reactNativeReanimated.interpolate)(toValue, [0, drawerWidth], [0, 1], _reactNativeReanimated.Extrapolation.CLAMP);
const normalizedInitialVelocity = (0, _reactNativeReanimated.interpolate)(initialVelocity, [0, drawerWidth], [0, 1], _reactNativeReanimated.Extrapolation.CLAMP);
openValue.value = (0, _reactNativeReanimated.withSpring)(normalizedToValue, {
overshootClamping: true,
velocity: normalizedInitialVelocity,
mass: animationSpeed ? 1 / animationSpeed : 1,
damping: 40,
stiffness: 500
}, finished => {
if (finished) {
emitStateChanged(DrawerState.IDLE, willShow);
(0, _reactNativeReanimated.runOnJS)(setDrawerOpened)(willShow);
(0, _reactNativeReanimated.runOnJS)(setDrawerState)(DrawerState.IDLE);
if (willShow) {
var _runOnJS2;
onDrawerOpen && ((_runOnJS2 = (0, _reactNativeReanimated.runOnJS)(onDrawerOpen)) === null || _runOnJS2 === void 0 ? void 0 : _runOnJS2());
} else {
var _runOnJS3;
onDrawerClose && ((_runOnJS3 = (0, _reactNativeReanimated.runOnJS)(onDrawerClose)) === null || _runOnJS3 === void 0 ? void 0 : _runOnJS3());
}
}
});
}, [openValue, emitStateChanged, isDrawerOpen, hideStatusBar, onDrawerClose, onDrawerOpen, drawerWidth, statusBarAnimation]);
const handleRelease = (0, _react.useCallback)(event => {
'worklet';
let {
translationX: dragX,
velocityX,
x: touchX
} = event;
if (drawerPosition !== DrawerPosition.LEFT) {
// See description in _updateAnimatedEvent about why events are flipped
// for right-side drawer
dragX = -dragX;
touchX = containerWidth - touchX;
velocityX = -velocityX;
}
const gestureStartX = touchX - dragX;
let dragOffsetBasedOnStart = 0;
if (drawerType === DrawerType.FRONT) {
dragOffsetBasedOnStart = gestureStartX > drawerWidth ? gestureStartX - drawerWidth : 0;
}
const startOffsetX = dragX + dragOffsetBasedOnStart + (isDrawerOpen.value ? drawerWidth : 0);
const projOffsetX = startOffsetX + DRAG_TOSS * velocityX;
const shouldOpen = projOffsetX > drawerWidth / 2;
if (shouldOpen) {
animateDrawer(drawerWidth, velocityX);
} else {
animateDrawer(0, velocityX);
}
}, [animateDrawer, containerWidth, drawerPosition, drawerType, drawerWidth, isDrawerOpen]);
const openDrawer = (0, _react.useCallback)((options = {}) => {
'worklet';
var _options$initialVeloc;
animateDrawer(drawerWidth, (_options$initialVeloc = options.initialVelocity) !== null && _options$initialVeloc !== void 0 ? _options$initialVeloc : 0, options.animationSpeed);
}, [animateDrawer, drawerWidth]);
const closeDrawer = (0, _react.useCallback)((options = {}) => {
'worklet';
var _options$initialVeloc2;
animateDrawer(0, (_options$initialVeloc2 = options.initialVelocity) !== null && _options$initialVeloc2 !== void 0 ? _options$initialVeloc2 : 0, options.animationSpeed);
}, [animateDrawer]);
const overlayDismissGesture = (0, _react.useMemo)(() => _gestureObjects.GestureObjects.Tap().maxDistance(25).onEnd(() => {
if (isDrawerOpen.value && drawerLockMode !== DrawerLockMode.LOCKED_OPEN) {
closeDrawer();
}
}), [closeDrawer, isDrawerOpen, drawerLockMode]);
const overlayAnimatedStyle = (0, _reactNativeReanimated.useAnimatedStyle)(() => ({
opacity: openValue.value,
backgroundColor: overlayColor
}));
const fillHitSlop = (0, _react.useMemo)(() => isFromLeft ? {
left: drawerWidth
} : {
right: drawerWidth
}, [drawerWidth, isFromLeft]);
const panGesture = (0, _react.useMemo)(() => {
return _gestureObjects.GestureObjects.Pan().activeCursor(activeCursor).mouseButton(mouseButton).hitSlop(drawerOpened ? fillHitSlop : edgeHitSlop).minDistance(drawerOpened ? 100 : 0).activeOffsetX(gestureOrientation * minSwipeDistance).failOffsetY([-15, 15]).simultaneousWithExternalGesture(overlayDismissGesture).enableTrackpadTwoFingerGesture(enableTrackpadTwoFingerGesture).enabled(drawerState !== DrawerState.SETTLING && (drawerOpened ? drawerLockMode !== DrawerLockMode.LOCKED_OPEN : drawerLockMode !== DrawerLockMode.LOCKED_CLOSED)).onStart(() => {
emitStateChanged(DrawerState.DRAGGING, false);
(0, _reactNativeReanimated.runOnJS)(setDrawerState)(DrawerState.DRAGGING);
if (keyboardDismissMode === DrawerKeyboardDismissMode.ON_DRAG) {
(0, _reactNativeReanimated.runOnJS)(_reactNative.Keyboard.dismiss)();
}
if (hideStatusBar) {
(0, _reactNativeReanimated.runOnJS)(_reactNative.StatusBar.setHidden)(true, statusBarAnimation);
}
}).onUpdate(event => {
const startedOutsideTranslation = isFromLeft ? (0, _reactNativeReanimated.interpolate)(event.x, [0, drawerWidth, drawerWidth + 1], [0, drawerWidth, drawerWidth]) : (0, _reactNativeReanimated.interpolate)(event.x - containerWidth, [-drawerWidth - 1, -drawerWidth, 0], [drawerWidth, drawerWidth, 0]);
const startedInsideTranslation = sideCorrection * (event.translationX + (drawerOpened ? drawerWidth * -gestureOrientation : 0));
const adjustedTranslation = Math.max(drawerOpened ? startedOutsideTranslation : 0, startedInsideTranslation);
openValue.value = (0, _reactNativeReanimated.interpolate)(adjustedTranslation, [-drawerWidth, 0, drawerWidth], [1, 0, 1], _reactNativeReanimated.Extrapolation.CLAMP);
}).onEnd(handleRelease);
}, [drawerLockMode, openValue, drawerWidth, emitStateChanged, gestureOrientation, handleRelease, edgeHitSlop, fillHitSlop, minSwipeDistance, hideStatusBar, keyboardDismissMode, overlayDismissGesture, drawerOpened, isFromLeft, containerWidth, sideCorrection, drawerState, activeCursor, enableTrackpadTwoFingerGesture, mouseButton, statusBarAnimation]); // When using RTL, row and row-reverse flex directions are flipped.
const reverseContentDirection = _reactNative.I18nManager.isRTL ? isFromLeft : !isFromLeft;
const dynamicDrawerStyles = {
backgroundColor: drawerBackgroundColor,
width: drawerWidth
};
const containerStyles = (0, _reactNativeReanimated.useAnimatedStyle)(() => {
if (drawerType === DrawerType.FRONT) {
return {};
}
return {
transform: [{
translateX: (0, _reactNativeReanimated.interpolate)(openValue.value, [0, 1], [0, drawerWidth * sideCorrection], _reactNativeReanimated.Extrapolation.CLAMP)
}]
};
});
const drawerAnimatedStyle = (0, _reactNativeReanimated.useAnimatedStyle)(() => {
const closedDrawerOffset = drawerWidth * -sideCorrection;
const isBack = drawerType === DrawerType.BACK;
const isIdle = drawerState === DrawerState.IDLE;
if (isBack) {
return {
transform: [{
translateX: 0
}],
flexDirection: reverseContentDirection ? 'row-reverse' : 'row'
};
}
let translateX = 0;
if (isIdle) {
translateX = drawerOpened ? 0 : closedDrawerOffset;
} else {
translateX = (0, _reactNativeReanimated.interpolate)(openValue.value, [0, 1], [closedDrawerOffset, 0], _reactNativeReanimated.Extrapolation.CLAMP);
}
return {
transform: [{
translateX
}],
flexDirection: reverseContentDirection ? 'row-reverse' : 'row'
};
});
const containerAnimatedProps = (0, _reactNativeReanimated.useAnimatedProps)(() => ({
importantForAccessibility: _reactNative.Platform.OS === 'android' ? isDrawerOpen.value ? 'no-hide-descendants' : 'yes' : undefined
}));
const children = typeof props.children === 'function' ? props.children(openValue) // renderer function
: props.children;
(0, _react.useImperativeHandle)(ref, () => ({
openDrawer,
closeDrawer
}), [openDrawer, closeDrawer]);
return /*#__PURE__*/_react.default.createElement(_GestureDetector.GestureDetector, {
gesture: panGesture,
userSelect: userSelect,
enableContextMenu: enableContextMenu
}, /*#__PURE__*/_react.default.createElement(_reactNativeReanimated.default.View, {
style: styles.main,
onLayout: handleContainerLayout
}, /*#__PURE__*/_react.default.createElement(_GestureDetector.GestureDetector, {
gesture: overlayDismissGesture
}, /*#__PURE__*/_react.default.createElement(_reactNativeReanimated.default.View, {
style: [drawerType === DrawerType.FRONT ? styles.containerOnBack : styles.containerInFront, containerStyles, contentContainerStyle],
animatedProps: containerAnimatedProps
}, children, /*#__PURE__*/_react.default.createElement(_reactNativeReanimated.default.View, {
animatedProps: overlayAnimatedProps,
style: [styles.overlay, overlayAnimatedStyle]
}))), /*#__PURE__*/_react.default.createElement(_reactNativeReanimated.default.View, {
pointerEvents: "box-none",
animatedProps: drawerAnimatedProps,
style: [styles.drawerContainer, drawerAnimatedStyle, drawerContainerStyle]
}, /*#__PURE__*/_react.default.createElement(_reactNativeReanimated.default.View, {
style: dynamicDrawerStyles
}, renderNavigationView(openValue)))));
});
var _default = DrawerLayout;
exports.default = _default;
const styles = _reactNative.StyleSheet.create({
drawerContainer: { ..._reactNative.StyleSheet.absoluteFillObject,
zIndex: 1001,
flexDirection: 'row'
},
containerInFront: { ..._reactNative.StyleSheet.absoluteFillObject,
zIndex: 1002
},
containerOnBack: { ..._reactNative.StyleSheet.absoluteFillObject
},
main: {
flex: 1,
zIndex: 0,
overflow: 'hidden'
},
overlay: { ..._reactNative.StyleSheet.absoluteFillObject,
zIndex: 1000
}
});
//# sourceMappingURL=ReanimatedDrawerLayout.js.map