react-native-navigation-bottom-sheet
Version:
A performant customizable bottom sheet component made on top of wix react-native-navigation library.
474 lines (368 loc) • 21.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var React = _interopRequireWildcard(require("react"));
var _reactNative = require("react-native");
var _reactNativeNavigation = require("react-native-navigation");
var _reactNativeReanimated = _interopRequireDefault(require("react-native-reanimated"));
var _reactNativeGestureHandler = require("react-native-gesture-handler");
var _events = require("./events");
var _utility = require("./utility");
var _AnimatedStoreScrolling = require("./AnimatedStoreScrolling");
var _AnimatedStoreSheet = require("./AnimatedStoreSheet");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && 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 {
call,
cond,
greaterThan,
lessThan,
neq,
clockRunning,
abs,
not,
and,
set,
sub,
or,
stopClock,
lessOrEq,
proc,
add,
max,
min,
eq,
multiply,
block,
onChange
} = _reactNativeReanimated.default;
const {
interpolate: interpolateDeprecated,
// @ts-ignore: this property is only present in Reanimated 2
interpolateNode
} = _reactNativeReanimated.default; // @ts-ignore
const interpolate = interpolateNode !== null && interpolateNode !== void 0 ? interpolateNode : interpolateDeprecated;
const screenHeight = _reactNative.Dimensions.get('window').height;
class BottomSheet extends React.Component {
/* BottomSheet state */
/** -------------------------------------------- */
/** Declaration of variables */
/** -------------------------------------------- */
/* Numerical values of snap points which are provided in props */
/* Flag indicating that a command to close the bottom sheet was initiated. */
/* Animated value responsible for an animation of the bottom sheet */
/* Clock used for snapping animations of the bottom sheet */
/* Snap points mapped to Animated values */
/* Value of a current dragging postion */
/* Value of a current speed of dragging */
/* State of a gesture */
/* Basically last snap point */
/* A flag forcing an animation to run; variables below define the animation */
/* Forcefully set starting point of a spring animation */
/* Forcefully set destination point of a spring animation */
/* Gesture mapping */
/* Opacity of the view outside of the bottom sheet */
/* @ts-ignore Dragging animation for bottom sheet, calculated as offset + dragging value */
constructor(props) {
var _this$props$enabledCo;
super(props);
_defineProperty(this, "state", {
heightOfHeader: 0,
contentHeight: new _reactNativeReanimated.default.Value(0),
screenHeight: screenHeight
});
_defineProperty(this, "snapPoints", (0, _utility.normalizeSnapPoints)(this.props.snapPoints, screenHeight));
_defineProperty(this, "topSnap", this.snapPoints[this.snapPoints.length - 1]);
_defineProperty(this, "closed", false);
_defineProperty(this, "_masterTranslateY", void 0);
_defineProperty(this, "_clock", new _reactNativeReanimated.default.Clock());
_defineProperty(this, "_snapPoints", []);
_defineProperty(this, "_dragY", new _reactNativeReanimated.default.Value(0));
_defineProperty(this, "_velocityY", new _reactNativeReanimated.default.Value(0));
_defineProperty(this, "_panState", new _reactNativeReanimated.default.Value(_reactNativeGestureHandler.State.END));
_defineProperty(this, "_lastBottomSheetHeight", new _reactNativeReanimated.default.Value(this.snapPoints[this.props.initialSnapIndex ? this.props.initialSnapIndex : this.snapPoints.length - 1]));
_defineProperty(this, "_forcedSet", new _reactNativeReanimated.default.Value(1));
_defineProperty(this, "_startSnapPoint", new _reactNativeReanimated.default.Value(screenHeight - this.snapPoints[0]));
_defineProperty(this, "_endSnapPoint", new _reactNativeReanimated.default.Value(screenHeight - this.snapPoints[this.props.initialSnapIndex ? this.props.initialSnapIndex : this.snapPoints.length - 1]));
_defineProperty(this, "_resetValues", new _reactNativeReanimated.default.Value(1));
_defineProperty(this, "_onGestureEvent", _reactNativeReanimated.default.event([{
nativeEvent: {
translationY: this._dragY,
velocityY: this._velocityY,
state: this._panState
}
}]));
_defineProperty(this, "_masterOpacity", void 0);
_defineProperty(this, "_draggingAnimation", interpolate(_reactNativeReanimated.default.add(_reactNativeReanimated.default.sub(screenHeight, this._lastBottomSheetHeight), add(this._dragY, _AnimatedStoreSheet.AnimatedStoreSheet._scrollToDragVal)), {
inputRange: [screenHeight - this.snapPoints[this.snapPoints.length - 1], screenHeight - this.snapPoints[0]],
outputRange: [screenHeight - this.snapPoints[this.snapPoints.length - 1], screenHeight - this.snapPoints[0]],
extrapolate: _reactNativeReanimated.default.Extrapolate.CLAMP
}));
_defineProperty(this, "handleTapEvent", ({
event
}) => {// if (!this.props.enabledContentGestureInteraction) {
// this._velocityScrollY.setValue(0);
// this._velocityY.setValue(0);
// }
// console.log('Tapped');
});
_defineProperty(this, "unsubscribeDismissBottomSheet", void 0);
_defineProperty(this, "unsubscribeSnapTo", void 0);
_defineProperty(this, "unsubscribeNavigationListener", void 0);
_defineProperty(this, "unsubscribeNavigationBackButtonListener", void 0);
_defineProperty(this, "registerListeners", () => {
this.unsubscribeSnapTo = (0, _events.listen)('BOTTOM_SHEET_SNAP_TO', index => {
this.snapTo(screenHeight - this.snapPoints[index]);
}); // Executes when the drawer needs to be dismissed
this.unsubscribeDismissBottomSheet = (0, _events.listen)('DISMISS_BOTTOM_SHEET', () => {
this.closeBottomSheet();
});
});
_defineProperty(this, "removeListeners", () => {
if (this.unsubscribeNavigationListener) this.unsubscribeNavigationListener.remove();
if (this.unsubscribeNavigationBackButtonListener) this.unsubscribeNavigationBackButtonListener.remove();
if (this.unsubscribeSnapTo) this.unsubscribeSnapTo();
if (this.unsubscribeDismissBottomSheet) this.unsubscribeDismissBottomSheet();
});
_defineProperty(this, "touchedOutside", () => {
const {
dismissWhenTouchOutside
} = this.props;
if (dismissWhenTouchOutside) {
this.closeBottomSheet();
}
});
_defineProperty(this, "snapTo", endPoint => {
this._endSnapPoint.setValue(endPoint);
this._startSnapPoint.setValue(_reactNativeReanimated.default.add(_reactNativeReanimated.default.sub(screenHeight, this._lastBottomSheetHeight), this._dragY));
this._forcedSet.setValue(1);
});
_defineProperty(this, "closeBottomSheet", () => {
if (this.closed) return;
this.closed = true;
this.snapTo(screenHeight - this.snapPoints[0]);
(0, _events.dispatch)('MARK_CLOSED');
setTimeout(() => {
_reactNativeNavigation.Navigation.dismissModal(this.props.componentId);
}, 250);
});
for (let i = 0; i < this.snapPoints.length; i++) {
this._snapPoints.push(new _reactNativeReanimated.default.Value(this.snapPoints[i]));
}
if (props.animationConfig) (0, _utility.overrideConfig)(props.animationConfig);
_AnimatedStoreScrolling.AnimatedStoreScrolling.init((_this$props$enabledCo = this.props.enabledContentGestureInteraction) !== null && _this$props$enabledCo !== void 0 ? _this$props$enabledCo : true, this.snapPoints); // animated_scroll_store._wasStarted.setValue(1);
const distance = _reactNativeReanimated.default.proc((bottomPoint, upperPoint) => abs(sub(bottomPoint, upperPoint)));
const toss = props.animationConfig && props.animationConfig.toss !== undefined ? props.animationConfig.toss : 0.28;
const actualVelocity = cond(eq(_AnimatedStoreScrolling.AnimatedStoreScrolling._lastState, 2), add(_AnimatedStoreSheet.AnimatedStoreSheet._velocityY, this._velocityY), this._velocityY);
const currentPoint = sub(this._lastBottomSheetHeight, add(this._dragY, _AnimatedStoreSheet.AnimatedStoreSheet._scrollToDragVal), multiply(toss, actualVelocity));
const rememberAndReturn = _reactNativeReanimated.default.proc(value => block([set(this._lastBottomSheetHeight, value), value]));
/**
* Recursive function finding closest snap point.
*/
const getClosestSnapPoint = (index = 0) => {
return index === this._snapPoints.length - 1 ? rememberAndReturn(this._snapPoints[index]) : cond(greaterThan(distance(this._snapPoints[index + 1], currentPoint), distance(currentPoint, this._snapPoints[index])), rememberAndReturn(this._snapPoints[index]), getClosestSnapPoint(index + 1));
}; // Does not let the bottom sheet to go higher the highest snap point
// and below the lowest snap point
const limitedAnimatedValue = value => {
return cond(eq(this._lastBottomSheetHeight, this._snapPoints[0]), min(value, sub(screenHeight, this.snapPoints[0])), max(value, sub(screenHeight, this.snapPoints[this.snapPoints.length - 1])));
};
const generalDebug = (val, msg) => {
console.log(msg);
console.log(val[0]);
};
const snapPointUpdated = snapPoint => {
if (this.props.onChange) {
for (let i = 0; i < this.snapPoints.length; i++) if (this.snapPoints[i] === snapPoint[0]) {
this.props.onChange(i);
break;
}
}
if (snapPoint[0] === 0) this.closeBottomSheet();
};
const isRun = new _reactNativeReanimated.default.Value(0);
const storedResult = new _reactNativeReanimated.default.Value(0);
/**
* Run a function finding a snap point only once. Save result in storedResult variable.
*/
const runOnce = func => {
return cond(isRun, storedResult, [set(storedResult, func), set(isRun, 1), set(this._dragY, 0), cond(eq(_AnimatedStoreScrolling.AnimatedStoreScrolling._lastState, 2), set(this._velocityY, 0)), set(_AnimatedStoreSheet.AnimatedStoreSheet._scrollToDragVal, 0), set(_AnimatedStoreSheet.AnimatedStoreSheet._velocityY, 0), storedResult]);
};
const updateWhenFinished = block([cond(this._forcedSet, [set(this._lastBottomSheetHeight, sub(screenHeight, this._endSnapPoint)), set(this._forcedSet, 0)])]);
const currentMovingPoint = new _reactNativeReanimated.default.Value(0);
/**
* Stop the spring animation and reset some values.
*/
const __stopClock = block([set(this._lastBottomSheetHeight, sub(screenHeight, currentMovingPoint)), updateWhenFinished, set(this._velocityY, 0), set(_AnimatedStoreScrolling.AnimatedStoreScrolling._velocityScrollY, 0), set(isRun, 0), _reactNativeReanimated.default.stopClock(this._clock)]);
const runSpringAnimation = new _reactNativeReanimated.default.Value(0);
this._masterTranslateY = _reactNativeReanimated.default.block([
/**
* Debugging section.
*/
onChange(this._lastBottomSheetHeight, call([this._lastBottomSheetHeight], snapPointUpdated)), // onChange(
// ASBS._scrollToDragVal,
// call([ASBS._scrollToDragVal], (snapPoints: readonly number[]) =>
// console.log('Changed scrollToDrag: ' + snapPoints[0])
// )
// ),
// onChange(
// this._velocityY,
// call([this._velocityY], (val: any) => generalDebug(val ,"Velocity: ")),
// ),
/**
* Main code section.
*/
cond(this._resetValues, [set(_AnimatedStoreSheet.AnimatedStoreSheet._scrollToDragVal, 0), set(_AnimatedStoreScrolling.AnimatedStoreScrolling._panScrollState, _reactNativeGestureHandler.State.END), set(_AnimatedStoreScrolling.AnimatedStoreScrolling._scrollY, 0), set(_AnimatedStoreScrolling.AnimatedStoreScrolling._velocityScrollY, 0), set(_AnimatedStoreScrolling.AnimatedStoreScrolling._prevTransY, 0), set(_AnimatedStoreScrolling.AnimatedStoreScrolling._transY, 0), set(this._resetValues, 0)]),
/**
* Some values do not get reset for some reason, TODO: investigate why
* so we reset at the beginning stage when gesture has not
* been identified yet.
*/
cond(or(eq(_AnimatedStoreScrolling.AnimatedStoreScrolling._panScrollState, _reactNativeGestureHandler.State.BEGAN), eq(this._panState, _reactNativeGestureHandler.State.BEGAN)), [set(this._velocityY, 0), set(_AnimatedStoreScrolling.AnimatedStoreScrolling._velocityScrollY, 0), set(_AnimatedStoreSheet.AnimatedStoreSheet._scrollToDragVal, 0)]),
/**
* Run spring animation when there are no gestures active or
* when there is a forced call to run the animation.
*/
set(runSpringAnimation, or(and(or(eq(this._panState, _reactNativeGestureHandler.State.END), eq(this._panState, _reactNativeGestureHandler.State.CANCELLED), eq(this._panState, _reactNativeGestureHandler.State.FAILED)), or(eq(_AnimatedStoreScrolling.AnimatedStoreScrolling._panScrollState, _reactNativeGestureHandler.State.END), eq(_AnimatedStoreScrolling.AnimatedStoreScrolling._panScrollState, _reactNativeGestureHandler.State.CANCELLED), eq(_AnimatedStoreScrolling.AnimatedStoreScrolling._panScrollState, _reactNativeGestureHandler.State.FAILED))), and(greaterThan(this._forcedSet, 0), neq(this._panState, _reactNativeGestureHandler.State.ACTIVE)))),
/**
* Determines the current state: whether we need to scroll content or
* drag the sheet.
*/
cond(lessThan(sub(this._snapPoints[this._snapPoints.length - 1], sub(this._lastBottomSheetHeight, _AnimatedStoreSheet.AnimatedStoreSheet._scrollToDragVal, this._dragY)), 0.1), cond(and(greaterThan(_AnimatedStoreScrolling.AnimatedStoreScrolling._velocityScrollY, 0), eq(_AnimatedStoreScrolling.AnimatedStoreScrolling._transY, 0)), [set(_AnimatedStoreScrolling.AnimatedStoreScrolling._lastState, 2)], set(_AnimatedStoreScrolling.AnimatedStoreScrolling._lastState, 1)), set(_AnimatedStoreScrolling.AnimatedStoreScrolling._lastState, 2)),
/**
* We are always at dragging state when dragging the header.
*/
cond(or(eq(this._panState, _reactNativeGestureHandler.State.ACTIVE), eq(this._panState, _reactNativeGestureHandler.State.BEGAN)), [set(_AnimatedStoreScrolling.AnimatedStoreScrolling._lastState, 2), stopClock(_AnimatedStoreScrolling.AnimatedStoreScrolling._scrollingClock)]),
/**
* To cover the special case (when starting dragging at the point that is not at the top),
* we update utility variable ASS._startedAtTheTop.
*/
cond(eq(this._lastBottomSheetHeight, this.topSnap), set(_AnimatedStoreScrolling.AnimatedStoreScrolling._startedAtTheTop, 1), set(_AnimatedStoreScrolling.AnimatedStoreScrolling._startedAtTheTop, 0)), cond(runSpringAnimation, [(0, _utility.runSpring)(this._clock, limitedAnimatedValue(cond(this._forcedSet, this._startSnapPoint, add(sub(screenHeight, this._lastBottomSheetHeight), add(this._dragY, _AnimatedStoreSheet.AnimatedStoreSheet._scrollToDragVal)))), cond(this._forcedSet, this._endSnapPoint, sub(screenHeight, runOnce(getClosestSnapPoint()))), actualVelocity, updateWhenFinished, isRun, currentMovingPoint)], [cond(clockRunning(this._clock), __stopClock), this._draggingAnimation])]); // @ts-ignore
this._masterOpacity = interpolate(this._masterTranslateY, {
inputRange: [screenHeight - this.snapPoints[this.snapPoints.length - 1], screenHeight - this.snapPoints[0]],
outputRange: [this.props.fadeOpacity, 0],
extrapolate: _reactNativeReanimated.default.Extrapolate.CLAMP
});
this.unsubscribeNavigationListener = _reactNativeNavigation.Navigation.events().bindComponent(this);
this.unsubscribeNavigationBackButtonListener = _reactNativeNavigation.Navigation.events().registerNavigationButtonPressedListener(event => {
if (event.buttonId === 'RNN.hardwareBackButton' || event.buttonId === 'RNN.hardwareBack') {
this.closeBottomSheet();
}
});
this.unsubscribeSnapTo = (0, _events.listen)('BOTTOM_SHEET_SNAP_TO', index => {
this.snapTo(screenHeight - this.snapPoints[index]);
}); // Executes when the drawer needs to be dismissed
this.unsubscribeDismissBottomSheet = (0, _events.listen)('DISMISS_BOTTOM_SHEET', () => {
this.closeBottomSheet();
});
}
/** -------------------------------------------- */
/** Class methods */
/** -------------------------------------------- */
/**
* [ react-native-navigation method. ]
*
* Executed when the component is navigated to view.
*/
componentDidAppear() {// this.registerListeners();
}
/**
* [ react-native-navigation method. ]
*
* Executed when the component is navigated away from view.
*/
componentDidDisappear() {}
componentWillUnmount() {
this.removeListeners();
}
/**
* Registers all the listenrs for this component
*/
render() {
/** Props */
const {
style,
snapPoints
} = this.props;
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(_reactNativeGestureHandler.TapGestureHandler, {
maxDurationMs: 100000,
onHandlerStateChange: this.handleTapEvent
}, /*#__PURE__*/React.createElement(_reactNative.View, {
style: styles.containerStyle
}, /*#__PURE__*/React.createElement(_reactNative.TouchableWithoutFeedback, {
onPress: this.touchedOutside
}, /*#__PURE__*/React.createElement(_reactNativeReanimated.default.View, {
style: [styles.overlayStyle, {
opacity: this._masterOpacity
}]
})), /*#__PURE__*/React.createElement(_reactNativeReanimated.default.View, {
style: [{
backgroundColor: this.props.backgroundColor,
borderTopLeftRadius: this.props.borderRadius,
borderTopRightRadius: this.props.borderRadius,
transform: [{
translateY: this._masterTranslateY
}],
height: this.topSnap,
width: '100%'
}, style]
}, /*#__PURE__*/React.createElement(_reactNativeGestureHandler.PanGestureHandler, {
onGestureEvent: this._onGestureEvent,
onHandlerStateChange: this._onGestureEvent
}, /*#__PURE__*/React.createElement(_reactNativeReanimated.default.View, {
onLayout: _AnimatedStoreScrolling.AnimatedStoreScrolling.handleLayoutHeader,
style: {
zIndex: 101
}
}, this.props.renderHeader && this.props.renderHeader())), /*#__PURE__*/React.createElement(_reactNativeReanimated.default.View, {
style: {
overflow: 'hidden',
width: '100%',
height: '100%',
borderTopLeftRadius: this.props.borderRadius,
borderTopRightRadius: this.props.borderRadius
}
}, /*#__PURE__*/React.createElement(_reactNativeGestureHandler.PanGestureHandler, {
onGestureEvent: _AnimatedStoreScrolling.AnimatedStoreScrolling._onGestureEventScrolling,
onHandlerStateChange: _AnimatedStoreScrolling.AnimatedStoreScrolling._onGestureEventScrolling
}, /*#__PURE__*/React.createElement(_reactNativeReanimated.default.View, {
style: {
height: '100%'
}
}, /*#__PURE__*/React.createElement(_reactNativeReanimated.default.View, {
onLayout: _AnimatedStoreScrolling.AnimatedStoreScrolling.handleLayoutContent,
style: [{
transform: [{
translateY: _AnimatedStoreScrolling.AnimatedStoreScrolling._masterScrollY
}]
}]
}, this.props.renderContent && this.props.renderContent()))))))));
}
}
_defineProperty(BottomSheet, "defaultProps", {
snapPoints: [0, 100, 300, 500],
backgroundColor: '#FFF',
dismissWhenTouchOutside: true,
fadeOpacity: 0.7,
borderRadius: 0,
enabledContentGestureInteraction: true
});
var _default = (0, _reactNativeGestureHandler.gestureHandlerRootHOC)(BottomSheet);
exports.default = _default;
const styles = _reactNative.StyleSheet.create({
containerStyle: {
flex: 1,
flexDirection: 'row',
justifyContent: "center"
},
overlayStyle: {
position: 'absolute',
width: '100%',
height: '100%',
backgroundColor: '#000'
}
});
//# sourceMappingURL=BottomSheet.js.map