GT2/Ejectable/node_modules/react-native-gesture-handler/lib/commonjs/components/DrawerLayout.js

531 lines
21 KiB
JavaScript
Raw Normal View History

2021-08-16 00:14:59 +00:00
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var React = _interopRequireWildcard(require("react"));
var _invariant = _interopRequireDefault(require("invariant"));
var _reactNative = require("react-native");
var _gestureHandlers = require("../handlers/gestureHandlers");
var _State = require("../State");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
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; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
const DRAG_TOSS = 0.05;
const IDLE = 'Idle';
const DRAGGING = 'Dragging';
const SETTLING = 'Settling';
class DrawerLayout extends React.Component {
constructor(_props) {
super(_props);
_defineProperty(this, "openValue", void 0);
_defineProperty(this, "onGestureEvent", void 0);
_defineProperty(this, "accessibilityIsModalView", /*#__PURE__*/React.createRef());
_defineProperty(this, "pointerEventsView", /*#__PURE__*/React.createRef());
_defineProperty(this, "panGestureHandler", /*#__PURE__*/React.createRef());
_defineProperty(this, "drawerShown", false);
_defineProperty(this, "updateAnimatedEvent", (props, state) => {
// Event definition is based on
const {
drawerPosition,
drawerWidth,
drawerType
} = props;
const {
dragX: dragXValue,
touchX: touchXValue,
drawerTranslation,
containerWidth
} = state;
let dragX = dragXValue;
let touchX = touchXValue;
if (drawerPosition !== 'left') {
// Most of the code is written in a way to handle left-side drawer.
// In order to handle right-side drawer the only thing we need to
// do is to reverse events coming from gesture handler in a way they
// emulate left-side drawer gestures. E.g. dragX is simply -dragX, and
// touchX is calulcated by subtracing real touchX from the width of the
// container (such that when touch happens at the right edge the value
// is simply 0)
dragX = _reactNative.Animated.multiply(new _reactNative.Animated.Value(-1), dragXValue); // TODO(TS): (for all "as" in this file) make sure we can map this
touchX = _reactNative.Animated.add(new _reactNative.Animated.Value(containerWidth), _reactNative.Animated.multiply(new _reactNative.Animated.Value(-1), touchXValue)); // TODO(TS): make sure we can map this;
touchXValue.setValue(containerWidth);
} else {
touchXValue.setValue(0);
} // While closing the drawer when user starts gesture outside of its area (in greyed
// out part of the window), we want the drawer to follow only once finger reaches the
// edge of the drawer.
// E.g. on the diagram below drawer is illustrate by X signs and the greyed out area by
// dots. The touch gesture starts at '*' and moves left, touch path is indicated by
// an arrow pointing left
// 1) +---------------+ 2) +---------------+ 3) +---------------+ 4) +---------------+
// |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........|
// |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........|
// |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........|
// |XXXXXXXX|......| |XXXXXXXX|.<-*..| |XXXXXXXX|<--*..| |XXXXX|<-----*..|
// |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........|
// |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........|
// |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........|
// +---------------+ +---------------+ +---------------+ +---------------+
//
// For the above to work properly we define animated value that will keep start position
// of the gesture. Then we use that value to calculate how much we need to subtract from
// the dragX. If the gesture started on the greyed out area we take the distance from the
// edge of the drawer to the start position. Otherwise we don't subtract at all and the
// drawer be pulled back as soon as you start the pan.
//
// This is used only when drawerType is "front"
//
let translationX = dragX;
if (drawerType === 'front') {
const startPositionX = _reactNative.Animated.add(touchX, _reactNative.Animated.multiply(new _reactNative.Animated.Value(-1), dragX));
const dragOffsetFromOnStartPosition = startPositionX.interpolate({
inputRange: [drawerWidth - 1, drawerWidth, drawerWidth + 1],
outputRange: [0, 0, 1]
});
translationX = _reactNative.Animated.add(dragX, dragOffsetFromOnStartPosition); // TODO: as above
}
this.openValue = _reactNative.Animated.add(translationX, drawerTranslation).interpolate({
inputRange: [0, drawerWidth],
outputRange: [0, 1],
extrapolate: 'clamp'
});
const gestureOptions = {
useNativeDriver: props.useNativeAnimations
};
if (this.props.onDrawerSlide) {
gestureOptions.listener = ev => {
var _this$props$onDrawerS, _this$props;
const translationX = Math.floor(Math.abs(ev.nativeEvent.translationX));
const position = translationX / this.state.containerWidth;
(_this$props$onDrawerS = (_this$props = this.props).onDrawerSlide) === null || _this$props$onDrawerS === void 0 ? void 0 : _this$props$onDrawerS.call(_this$props, position);
};
}
this.onGestureEvent = _reactNative.Animated.event([{
nativeEvent: {
translationX: dragXValue,
x: touchXValue
}
}], gestureOptions);
});
_defineProperty(this, "handleContainerLayout", ({
nativeEvent
}) => {
this.setState({
containerWidth: nativeEvent.layout.width
});
});
_defineProperty(this, "emitStateChanged", (newState, drawerWillShow) => {
var _this$props$onDrawerS2, _this$props2;
(_this$props$onDrawerS2 = (_this$props2 = this.props).onDrawerStateChanged) === null || _this$props$onDrawerS2 === void 0 ? void 0 : _this$props$onDrawerS2.call(_this$props2, newState, drawerWillShow);
});
_defineProperty(this, "openingHandlerStateChange", ({
nativeEvent
}) => {
if (nativeEvent.oldState === _State.State.ACTIVE) {
this.handleRelease({
nativeEvent
});
} else if (nativeEvent.state === _State.State.ACTIVE) {
this.emitStateChanged(DRAGGING, false);
if (this.props.keyboardDismissMode === 'on-drag') {
_reactNative.Keyboard.dismiss();
}
if (this.props.hideStatusBar) {
_reactNative.StatusBar.setHidden(true, this.props.statusBarAnimation || 'slide');
}
}
});
_defineProperty(this, "onTapHandlerStateChange", ({
nativeEvent
}) => {
if (this.drawerShown && nativeEvent.oldState === _State.State.ACTIVE && this.props.drawerLockMode !== 'locked-open') {
this.closeDrawer();
}
});
_defineProperty(this, "handleRelease", ({
nativeEvent
}) => {
const {
drawerWidth,
drawerPosition,
drawerType
} = this.props;
const {
containerWidth
} = this.state;
let {
translationX: dragX,
velocityX,
x: touchX
} = nativeEvent;
if (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 === 'front') {
dragOffsetBasedOnStart = gestureStartX > drawerWidth ? gestureStartX - drawerWidth : 0;
}
const startOffsetX = dragX + dragOffsetBasedOnStart + (this.drawerShown ? drawerWidth : 0);
const projOffsetX = startOffsetX + DRAG_TOSS * velocityX;
const shouldOpen = projOffsetX > drawerWidth / 2;
if (shouldOpen) {
this.animateDrawer(startOffsetX, drawerWidth, velocityX);
} else {
this.animateDrawer(startOffsetX, 0, velocityX);
}
});
_defineProperty(this, "updateShowing", showing => {
var _this$accessibilityIs, _this$pointerEventsVi, _this$panGestureHandl;
this.drawerShown = showing;
(_this$accessibilityIs = this.accessibilityIsModalView.current) === null || _this$accessibilityIs === void 0 ? void 0 : _this$accessibilityIs.setNativeProps({
accessibilityViewIsModal: showing
});
(_this$pointerEventsVi = this.pointerEventsView.current) === null || _this$pointerEventsVi === void 0 ? void 0 : _this$pointerEventsVi.setNativeProps({
pointerEvents: showing ? 'auto' : 'none'
});
const {
drawerPosition,
minSwipeDistance,
edgeWidth
} = this.props;
const fromLeft = drawerPosition === 'left'; // gestureOrientation is 1 if the expected gesture is from left to right and -1 otherwise
// e.g. when drawer is on the left and is closed we expect left to right gesture, thus
// orientation will be 1.
const gestureOrientation = (fromLeft ? 1 : -1) * (this.drawerShown ? -1 : 1); // When drawer is closed we want the hitSlop to be horizontally shorter
// than the container size by the value of SLOP. This will make it only
// activate when gesture happens not further than SLOP away from the edge
const hitSlop = fromLeft ? {
left: 0,
width: showing ? undefined : edgeWidth
} : {
right: 0,
width: showing ? undefined : edgeWidth
}; // @ts-ignore internal API, maybe could be fixed in handler types
(_this$panGestureHandl = this.panGestureHandler.current) === null || _this$panGestureHandl === void 0 ? void 0 : _this$panGestureHandl.setNativeProps({
hitSlop,
activeOffsetX: gestureOrientation * minSwipeDistance
});
});
_defineProperty(this, "animateDrawer", (fromValue, toValue, velocity, speed) => {
this.state.dragX.setValue(0);
this.state.touchX.setValue(this.props.drawerPosition === 'left' ? 0 : this.state.containerWidth);
if (fromValue != null) {
let nextFramePosition = fromValue;
if (this.props.useNativeAnimations) {
// When using native driver, we predict the next position of the animation
// because it takes one frame of a roundtrip to pass RELEASE event from
// native driver to JS before we can start animating. Without it, it is more
// noticable that the frame is dropped.
if (fromValue < toValue && velocity > 0) {
nextFramePosition = Math.min(fromValue + velocity / 60.0, toValue);
} else if (fromValue > toValue && velocity < 0) {
nextFramePosition = Math.max(fromValue + velocity / 60.0, toValue);
}
}
this.state.drawerTranslation.setValue(nextFramePosition);
}
const willShow = toValue !== 0;
this.updateShowing(willShow);
this.emitStateChanged(SETTLING, willShow);
if (this.props.hideStatusBar) {
_reactNative.StatusBar.setHidden(willShow, this.props.statusBarAnimation || 'slide');
}
_reactNative.Animated.spring(this.state.drawerTranslation, {
velocity,
bounciness: 0,
toValue,
useNativeDriver: this.props.useNativeAnimations,
speed: speed !== null && speed !== void 0 ? speed : undefined
}).start(({
finished
}) => {
if (finished) {
this.emitStateChanged(IDLE, willShow);
if (willShow) {
var _this$props$onDrawerO, _this$props3;
(_this$props$onDrawerO = (_this$props3 = this.props).onDrawerOpen) === null || _this$props$onDrawerO === void 0 ? void 0 : _this$props$onDrawerO.call(_this$props3);
} else {
var _this$props$onDrawerC, _this$props4;
(_this$props$onDrawerC = (_this$props4 = this.props).onDrawerClose) === null || _this$props$onDrawerC === void 0 ? void 0 : _this$props$onDrawerC.call(_this$props4);
}
}
});
});
_defineProperty(this, "openDrawer", (options = {}) => {
this.animateDrawer( // TODO: decide if it should be null or undefined is the proper value
undefined, this.props.drawerWidth, options.velocity ? options.velocity : 0); // We need to force the update, otherwise the overlay is not rerendered and it would not be clickable
this.forceUpdate();
});
_defineProperty(this, "closeDrawer", (options = {}) => {
// TODO: decide if it should be null or undefined is the proper value
this.animateDrawer(undefined, 0, options.velocity ? options.velocity : 0); // We need to force the update, otherwise the overlay is not rerendered and it would be still clickable
this.forceUpdate();
});
_defineProperty(this, "renderOverlay", () => {
/* Overlay styles */
(0, _invariant.default)(this.openValue, 'should be set');
const overlayOpacity = this.openValue.interpolate({
inputRange: [0, 1],
outputRange: [0, 1],
extrapolate: 'clamp'
});
const dynamicOverlayStyles = {
opacity: overlayOpacity,
backgroundColor: this.props.overlayColor
};
return /*#__PURE__*/React.createElement(_gestureHandlers.TapGestureHandler, {
onHandlerStateChange: this.onTapHandlerStateChange
}, /*#__PURE__*/React.createElement(_reactNative.Animated.View, {
pointerEvents: this.drawerShown ? 'auto' : 'none',
ref: this.pointerEventsView,
style: [styles.overlay, dynamicOverlayStyles]
}));
});
_defineProperty(this, "renderDrawer", () => {
const {
drawerBackgroundColor,
drawerWidth,
drawerPosition,
drawerType,
drawerContainerStyle,
contentContainerStyle
} = this.props;
const fromLeft = drawerPosition === 'left';
const drawerSlide = drawerType !== 'back';
const containerSlide = drawerType !== 'front'; // we rely on row and row-reverse flex directions to position the drawer
// properly. Apparently for RTL these are flipped which requires us to use
// the opposite setting for the drawer to appear from left or right according
// to the drawerPosition prop
const reverseContentDirection = _reactNative.I18nManager.isRTL ? fromLeft : !fromLeft;
const dynamicDrawerStyles = {
backgroundColor: drawerBackgroundColor,
width: drawerWidth
};
const openValue = this.openValue;
(0, _invariant.default)(openValue, 'should be set');
let containerStyles;
if (containerSlide) {
const containerTranslateX = openValue.interpolate({
inputRange: [0, 1],
outputRange: fromLeft ? [0, drawerWidth] : [0, -drawerWidth],
extrapolate: 'clamp'
});
containerStyles = {
transform: [{
translateX: containerTranslateX
}]
};
}
let drawerTranslateX = 0;
if (drawerSlide) {
const closedDrawerOffset = fromLeft ? -drawerWidth : drawerWidth;
drawerTranslateX = openValue.interpolate({
inputRange: [0, 1],
outputRange: [closedDrawerOffset, 0],
extrapolate: 'clamp'
});
}
const drawerStyles = {
transform: [{
translateX: drawerTranslateX
}],
flexDirection: reverseContentDirection ? 'row-reverse' : 'row'
};
return /*#__PURE__*/React.createElement(_reactNative.Animated.View, {
style: styles.main,
onLayout: this.handleContainerLayout
}, /*#__PURE__*/React.createElement(_reactNative.Animated.View, {
style: [drawerType === 'front' ? styles.containerOnBack : styles.containerInFront, containerStyles, contentContainerStyle],
importantForAccessibility: this.drawerShown ? 'no-hide-descendants' : 'yes'
}, typeof this.props.children === 'function' ? this.props.children(this.openValue) : this.props.children, this.renderOverlay()), /*#__PURE__*/React.createElement(_reactNative.Animated.View, {
pointerEvents: "box-none",
ref: this.accessibilityIsModalView,
accessibilityViewIsModal: this.drawerShown,
style: [styles.drawerContainer, drawerStyles, drawerContainerStyle]
}, /*#__PURE__*/React.createElement(_reactNative.View, {
style: dynamicDrawerStyles
}, this.props.renderNavigationView(this.openValue))));
});
_defineProperty(this, "setPanGestureRef", ref => {
var _this$props$onGesture, _this$props5;
// TODO(TS): make sure it is OK
// taken from https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31065#issuecomment-596081842
this.panGestureHandler.current = ref;
(_this$props$onGesture = (_this$props5 = this.props).onGestureRef) === null || _this$props$onGesture === void 0 ? void 0 : _this$props$onGesture.call(_this$props5, ref);
});
const _dragX = new _reactNative.Animated.Value(0);
const _touchX = new _reactNative.Animated.Value(0);
const _drawerTranslation = new _reactNative.Animated.Value(0);
this.state = {
dragX: _dragX,
touchX: _touchX,
drawerTranslation: _drawerTranslation,
containerWidth: 0
};
this.updateAnimatedEvent(_props, this.state);
}
UNSAFE_componentWillUpdate(props, state) {
if (this.props.drawerPosition !== props.drawerPosition || this.props.drawerWidth !== props.drawerWidth || this.props.drawerType !== props.drawerType || this.state.containerWidth !== state.containerWidth) {
this.updateAnimatedEvent(props, state);
}
}
render() {
const {
drawerPosition,
drawerLockMode,
edgeWidth,
minSwipeDistance
} = this.props;
const fromLeft = drawerPosition === 'left'; // gestureOrientation is 1 if the expected gesture is from left to right and -1 otherwise
// e.g. when drawer is on the left and is closed we expect left to right gesture, thus
// orientation will be 1.
const gestureOrientation = (fromLeft ? 1 : -1) * (this.drawerShown ? -1 : 1); // When drawer is closed we want the hitSlop to be horizontally shorter
// than the container size by the value of SLOP. This will make it only
// activate when gesture happens not further than SLOP away from the edge
const hitSlop = fromLeft ? {
left: 0,
width: this.drawerShown ? undefined : edgeWidth
} : {
right: 0,
width: this.drawerShown ? undefined : edgeWidth
};
return /*#__PURE__*/React.createElement(_gestureHandlers.PanGestureHandler // @ts-ignore could be fixed in handler types
, {
ref: this.setPanGestureRef,
hitSlop: hitSlop,
activeOffsetX: gestureOrientation * minSwipeDistance,
failOffsetY: [-15, 15],
onGestureEvent: this.onGestureEvent,
onHandlerStateChange: this.openingHandlerStateChange,
enableTrackpadTwoFingerGesture: this.props.enableTrackpadTwoFingerGesture,
enabled: drawerLockMode !== 'locked-closed' && drawerLockMode !== 'locked-open'
}, this.renderDrawer());
}
}
exports.default = DrawerLayout;
_defineProperty(DrawerLayout, "defaultProps", {
drawerWidth: 200,
drawerPosition: 'left',
useNativeAnimations: true,
drawerType: 'front',
edgeWidth: 20,
minSwipeDistance: 3,
overlayColor: 'rgba(0, 0, 0, 0.7)',
drawerLockMode: 'unlocked',
enableTrackpadTwoFingerGesture: false
});
_defineProperty(DrawerLayout, "positions", {
Left: 'left',
Right: 'right'
});
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=DrawerLayout.js.map