stream-chat-react-native-core
Version:
The official React Native and Expo components for Stream Chat, a service for building chat applications
465 lines • 20.3 kB
JavaScript
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.BottomSheetModal = void 0;
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
var _react = _interopRequireWildcard(require("react"));
var _reactNative = require("react-native");
var _reactNativeGestureHandler = require("react-native-gesture-handler");
var _reactNativeReanimated = _interopRequireWildcard(require("react-native-reanimated"));
var _reactNativeSafeAreaContext = require("react-native-safe-area-context");
var _BottomSheetModal = require("./BottomSheetModal.utils");
var _useA11yLabel = require("../../a11y/hooks/useA11yLabel");
var _useAnnounceOnShow = require("../../a11y/hooks/useAnnounceOnShow");
var _useResolvedModalAccessibilityProps = require("../../a11y/hooks/useResolvedModalAccessibilityProps");
var _BottomSheetContext = require("../../contexts/bottomSheetContext/BottomSheetContext");
var _ThemeContext = require("../../contexts/themeContext/ThemeContext");
var _hooks = require("../../hooks");
var _theme = require("../../theme");
var _jsxRuntime = require("react/jsx-runtime");
var _jsxFileName = "/home/runner/work/stream-chat-react-native/stream-chat-react-native/package/src/components/UIComponents/BottomSheetModal.tsx";
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 (var _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); }
var supportedOrientations = ['portrait', 'portrait-upside-down', 'landscape', 'landscape-left', 'landscape-right'];
var LANDSCAPE_MAX_HEIGHT_RATIO = 0.9;
var BottomSheetModalInner = props => {
var _useWindowDimensions = (0, _reactNative.useWindowDimensions)(),
windowHeight = _useWindowDimensions.height;
var _useSafeAreaInsets = (0, _reactNativeSafeAreaContext.useSafeAreaInsets)(),
topInset = _useSafeAreaInsets.top,
bottomInset = _useSafeAreaInsets.bottom;
var children = props.children,
_props$enableDynamicS = props.enableDynamicSizing,
enableDynamicSizing = _props$enableDynamicS === void 0 ? false : _props$enableDynamicS,
_props$height = props.height,
height = _props$height === void 0 ? windowHeight / 2 : _props$height,
onClose = props.onClose,
visible = props.visible,
_props$lazy = props.lazy,
lazy = _props$lazy === void 0 ? false : _props$lazy;
var _useTheme = (0, _ThemeContext.useTheme)(),
_useTheme$theme$botto = _useTheme.theme.bottomSheetModal,
container = _useTheme$theme$botto.container,
contentContainer = _useTheme$theme$botto.contentContainer,
handle = _useTheme$theme$botto.handle,
overlayTheme = _useTheme$theme$botto.overlay;
var styles = useStyles();
var isLandscape = (0, _hooks.useScreenOrientation)() === 'landscape';
var availableHeight = Math.max(0, windowHeight - topInset - (_reactNative.Platform.OS === 'android' ? bottomInset + 16 : 0));
var maxHeight = isLandscape ? Math.round(availableHeight * LANDSCAPE_MAX_HEIGHT_RATIO) : availableHeight;
var fixedBaseHeight = Math.min(height, maxHeight);
var contentHeight = (0, _reactNativeReanimated.useSharedValue)(undefined);
var baseHeight = (0, _reactNativeReanimated.useDerivedValue)(() => (0, _BottomSheetModal.getBottomSheetBaseHeight)({
contentHeight: contentHeight.value,
enableDynamicSizing,
height,
maxHeight
}), [contentHeight, enableDynamicSizing, height, maxHeight]);
var topSnapIndex = (0, _reactNativeReanimated.useDerivedValue)(() => (0, _BottomSheetModal.getBottomSheetTopSnapIndex)({
baseHeight: baseHeight.value,
maxHeight
}), [baseHeight, maxHeight]);
var sheetTranslateY = (0, _reactNativeReanimated.useSharedValue)(maxHeight);
var keyboardOffset = (0, _reactNativeReanimated.useSharedValue)(0);
var currentSnapIndex = (0, _reactNativeReanimated.useSharedValue)(0);
var isOpen = (0, _reactNativeReanimated.useSharedValue)(false);
var isOpening = (0, _reactNativeReanimated.useSharedValue)(false);
var panStartTranslateY = (0, _reactNativeReanimated.useSharedValue)(0);
var hasCommittedVisibilityRef = (0, _react.useRef)(false);
var wasVisibleRef = (0, _react.useRef)(false);
var _useState = (0, _react.useState)(!lazy),
_useState2 = (0, _slicedToArray2.default)(_useState, 2),
renderContent = _useState2[0],
setRenderContent = _useState2[1];
var _useState3 = (0, _react.useState)(false),
_useState4 = (0, _slicedToArray2.default)(_useState3, 2),
isDismissing = _useState4[0],
setIsDismissing = _useState4[1];
var pendingCloseCallbackRef = (0, _react.useRef)(undefined);
var showContent = (0, _hooks.useStableCallback)(() => {
if (lazy) {
setRenderContent(true);
}
});
var handleDynamicContentLayout = (0, _hooks.useStableCallback)(event => {
if (!enableDynamicSizing) {
return;
}
var nextContentHeight = Math.ceil(event.nativeEvent.layout.height);
if (contentHeight.value === nextContentHeight) {
return;
}
contentHeight.value = nextContentHeight;
});
var open = (0, _hooks.useStableCallback)((shouldShowContentAfterOpen = true) => {
sheetTranslateY.value = (0, _reactNativeReanimated.withTiming)(maxHeight - fixedBaseHeight, {
duration: 250,
easing: _reactNativeReanimated.Easing.out(_reactNativeReanimated.Easing.cubic)
}, finished => {
if (!finished) return;
isOpening.value = false;
if (shouldShowContentAfterOpen) {
(0, _reactNativeReanimated.runOnJS)(showContent)();
}
});
});
var handleNativeModalDismiss = (0, _hooks.useStableCallback)(() => {
var callback = pendingCloseCallbackRef.current;
pendingCloseCallbackRef.current = undefined;
onClose();
callback?.();
});
var finishClose = (0, _hooks.useStableCallback)(({
closeAnimationFinishedCallback,
shouldDismiss
}) => {
pendingCloseCallbackRef.current = closeAnimationFinishedCallback;
if (_reactNative.Platform.OS !== 'ios') {
setTimeout(handleNativeModalDismiss, 100);
} else {
if (shouldDismiss) {
setIsDismissing(true);
return;
}
handleNativeModalDismiss();
}
});
var closeFromGesture = (0, _hooks.useStableCallback)(() => {
requestAnimationFrame(() => {
isOpen.value = false;
isOpening.value = false;
sheetTranslateY.value = (0, _reactNativeReanimated.withTiming)(maxHeight, {
duration: 250,
easing: _reactNativeReanimated.Easing.out(_reactNativeReanimated.Easing.cubic)
}, finished => {
if (finished) {
(0, _reactNativeReanimated.runOnJS)(onClose)();
}
});
});
});
var closeInternal = (0, _hooks.useStableCallback)(({
closeAnimationFinishedCallback,
shouldDismiss
}) => {
if (!visible || !isOpen.value) {
return;
}
isOpen.value = false;
isOpening.value = false;
sheetTranslateY.value = (0, _reactNativeReanimated.withTiming)(maxHeight, {
duration: 250,
easing: _reactNativeReanimated.Easing.out(_reactNativeReanimated.Easing.cubic)
}, finished => {
if (finished) {
(0, _reactNativeReanimated.runOnJS)(finishClose)({
closeAnimationFinishedCallback,
shouldDismiss
});
}
});
});
var close = (0, _hooks.useStableCallback)(closeAnimationFinishedCallback => {
closeInternal({
closeAnimationFinishedCallback
});
});
var dismiss = (0, _hooks.useStableCallback)(dismissFinishedCallback => {
closeInternal({
closeAnimationFinishedCallback: dismissFinishedCallback,
shouldDismiss: true
});
});
(0, _react.useLayoutEffect)(() => {
var wasVisible = hasCommittedVisibilityRef.current ? wasVisibleRef.current : false;
hasCommittedVisibilityRef.current = true;
wasVisibleRef.current = visible;
if (!visible || wasVisible) {
return;
}
isOpen.value = true;
isOpening.value = true;
currentSnapIndex.value = 0;
sheetTranslateY.value = maxHeight;
if (enableDynamicSizing) {
setRenderContent(true);
return;
}
open();
}, [enableDynamicSizing, visible, isOpen, isOpening, maxHeight, open, sheetTranslateY, currentSnapIndex]);
(0, _reactNativeReanimated.useAnimatedReaction)(() => {
if (!visible || !enableDynamicSizing || !isOpening.value || contentHeight.value === undefined) {
return undefined;
}
return (0, _BottomSheetModal.getBottomSheetSnapPointTranslateY)({
baseHeight: baseHeight.value,
maxHeight,
snapIndex: 0
});
}, (nextTranslateY, prevTranslateY) => {
if (nextTranslateY === undefined || nextTranslateY === prevTranslateY) {
return;
}
sheetTranslateY.value = (0, _reactNativeReanimated.withTiming)(nextTranslateY, {
duration: 250,
easing: _reactNativeReanimated.Easing.out(_reactNativeReanimated.Easing.cubic)
}, finished => {
if (finished) {
isOpening.value = false;
}
});
}, [baseHeight, contentHeight, enableDynamicSizing, isOpening, maxHeight, sheetTranslateY, visible]);
(0, _react.useEffect)(() => {
if (visible) return;
isOpen.value = false;
isOpening.value = false;
keyboardOffset.value = 0;
currentSnapIndex.value = 0;
sheetTranslateY.value = maxHeight;
contentHeight.value = undefined;
setRenderContent(!lazy);
}, [contentHeight, visible, lazy, isOpen, isOpening, keyboardOffset, maxHeight, sheetTranslateY, currentSnapIndex]);
(0, _reactNativeReanimated.useAnimatedReaction)(() => {
if (!visible || !isOpen.value || isOpening.value) {
return undefined;
}
var clampedSnapIndex = Math.min(currentSnapIndex.value, topSnapIndex.value);
return {
snapIndex: clampedSnapIndex,
translateY: (0, _BottomSheetModal.getBottomSheetSnapPointTranslateY)({
baseHeight: baseHeight.value,
maxHeight,
snapIndex: clampedSnapIndex
})
};
}, nextTarget => {
if (!nextTarget) {
return;
}
if (currentSnapIndex.value !== nextTarget.snapIndex) {
currentSnapIndex.value = nextTarget.snapIndex;
}
if (Math.abs(sheetTranslateY.value - nextTarget.translateY) < 1) {
return;
}
sheetTranslateY.value = (0, _reactNativeReanimated.withTiming)(nextTarget.translateY, {
duration: 250,
easing: _reactNativeReanimated.Easing.inOut(_reactNativeReanimated.Easing.ease)
});
}, [baseHeight, currentSnapIndex, isOpen, isOpening, maxHeight, sheetTranslateY, topSnapIndex, visible]);
var animateKeyboardOffset = (0, _hooks.useStableCallback)(offset => {
keyboardOffset.value = (0, _reactNativeReanimated.withTiming)(offset, {
duration: 250,
easing: _reactNativeReanimated.Easing.inOut(_reactNativeReanimated.Easing.ease)
});
});
var keyboardDidShowRN = (0, _hooks.useStableCallback)(event => {
animateKeyboardOffset(event.endCoordinates.height);
});
var keyboardDidHide = (0, _hooks.useStableCallback)(() => {
animateKeyboardOffset(0);
});
(0, _react.useEffect)(() => {
if (!visible) return;
var listeners = [];
if (_reactNative.Platform.OS === 'ios') {
listeners.push(_reactNative.Keyboard.addListener('keyboardWillShow', keyboardDidShowRN), _reactNative.Keyboard.addListener('keyboardWillHide', keyboardDidHide));
}
return () => listeners.forEach(l => l.remove());
}, [visible, animateKeyboardOffset, keyboardDidHide, keyboardDidShowRN]);
var sheetViewportAnimatedStyle = (0, _reactNativeReanimated.useAnimatedStyle)(() => ({
transform: [{
translateY: sheetTranslateY.value - keyboardOffset.value
}]
}));
var overlayAnimatedStyle = (0, _reactNativeReanimated.useAnimatedStyle)(() => {
var visibleHeight = Math.max(0, maxHeight - sheetTranslateY.value);
var threshold = Math.max(1, Math.min(baseHeight.value, maxHeight));
var progress = Math.min(1, visibleHeight / threshold);
return {
opacity: progress
};
}, [baseHeight, maxHeight]);
var panGesture = (0, _react.useMemo)(() => _reactNativeGestureHandler.Gesture.Pan().enabled(renderContent).onBegin(() => {
panStartTranslateY.value = sheetTranslateY.value;
}).onUpdate(event => {
var nextTranslateY = panStartTranslateY.value + event.translationY;
sheetTranslateY.value = Math.min(Math.max(nextTranslateY, 0), maxHeight);
}).onEnd(event => {
var openTranslateY = (0, _BottomSheetModal.getBottomSheetSnapPointTranslateY)({
baseHeight: baseHeight.value,
maxHeight,
snapIndex: currentSnapIndex.value
});
var draggedDown = Math.max(sheetTranslateY.value - openTranslateY, 0);
var hasMultipleSnapPoints = topSnapIndex.value > 0;
var isAtTopSnap = currentSnapIndex.value === topSnapIndex.value;
var snap0TranslateY = (0, _BottomSheetModal.getBottomSheetSnapPointTranslateY)({
baseHeight: baseHeight.value,
maxHeight,
snapIndex: 0
});
var topSnapTranslateY = (0, _BottomSheetModal.getBottomSheetSnapPointTranslateY)({
baseHeight: baseHeight.value,
maxHeight,
snapIndex: topSnapIndex.value
});
var projectedTranslateY = sheetTranslateY.value + event.velocityY * 0.2;
var shouldCloseFromLowerSnap = event.velocityY > 500 || draggedDown > maxHeight / 2;
var shouldCloseFromTopSnap = event.velocityY > 2200 || projectedTranslateY > snap0TranslateY + (maxHeight - snap0TranslateY) * 0.96;
var shouldClose = !hasMultipleSnapPoints ? shouldCloseFromLowerSnap : isAtTopSnap ? shouldCloseFromTopSnap : shouldCloseFromLowerSnap;
if (shouldClose) {
(0, _reactNativeReanimated.runOnJS)(closeFromGesture)();
} else {
isOpen.value = true;
var nearestIndex = 0;
var minDistance = Math.abs(sheetTranslateY.value - snap0TranslateY);
if (hasMultipleSnapPoints) {
var topDistance = Math.abs(sheetTranslateY.value - topSnapTranslateY);
if (topDistance < minDistance) {
minDistance = topDistance;
nearestIndex = topSnapIndex.value;
}
}
if (hasMultipleSnapPoints && event.velocityY < -800) {
nearestIndex = topSnapIndex.value;
}
if (hasMultipleSnapPoints && isAtTopSnap && event.velocityY > 120) {
nearestIndex = 0;
}
currentSnapIndex.value = nearestIndex;
sheetTranslateY.value = (0, _reactNativeReanimated.withTiming)((0, _BottomSheetModal.getBottomSheetSnapPointTranslateY)({
baseHeight: baseHeight.value,
maxHeight,
snapIndex: nearestIndex
}), {
duration: 250,
easing: _reactNativeReanimated.Easing.inOut(_reactNativeReanimated.Easing.ease)
});
}
}), [baseHeight, currentSnapIndex, isOpen, maxHeight, closeFromGesture, panStartTranslateY, renderContent, sheetTranslateY, topSnapIndex]);
var onBackdropPress = (0, _hooks.useStableCallback)(() => close());
var modalA11yProps = (0, _useResolvedModalAccessibilityProps.useResolvedModalAccessibilityProps)();
var openAnnouncement = (0, _useA11yLabel.useA11yLabel)('a11y/Bottom sheet opened. Activate the close action or use the escape gesture to dismiss.');
(0, _useAnnounceOnShow.useAnnounceOnShow)(visible, openAnnouncement, {
delayMs: 800
});
var closeLabel = (0, _useA11yLabel.useA11yLabel)('a11y/Close');
var closeAccessibilityActions = (0, _react.useMemo)(() => closeLabel ? [{
label: closeLabel,
name: 'activate'
}] : undefined, [closeLabel]);
var onCloseAccessibilityAction = (0, _hooks.useStableCallback)(event => {
if (event.nativeEvent.actionName === 'activate') {
onClose();
}
});
var bottomSheetModalContextValue = (0, _react.useMemo)(() => ({
dismiss,
close,
currentSnapIndex,
topSnapIndex
}), [dismiss, close, currentSnapIndex, topSnapIndex]);
return (0, _jsxRuntime.jsx)(_reactNative.Modal, {
onDismiss: handleNativeModalDismiss,
onRequestClose: onClose,
supportedOrientations: supportedOrientations,
transparent: true,
visible: visible && !isDismissing,
children: (0, _jsxRuntime.jsx)(_reactNativeGestureHandler.GestureHandlerRootView, {
style: styles.sheetContentContainer,
children: (0, _jsxRuntime.jsxs)(_reactNative.View, {
style: [styles.overlay, overlayTheme],
children: [(0, _jsxRuntime.jsx)(_reactNativeReanimated.default.View, {
pointerEvents: "none",
style: [_reactNative.StyleSheet.absoluteFill, styles.backdrop, overlayAnimatedStyle]
}), (0, _jsxRuntime.jsx)(_reactNative.Pressable, {
onPress: onBackdropPress,
style: _reactNative.StyleSheet.absoluteFill
}), (0, _jsxRuntime.jsx)(_reactNativeReanimated.default.View, {
onAccessibilityEscape: onClose,
pointerEvents: "box-none",
style: [{
height: maxHeight
}, sheetViewportAnimatedStyle],
...modalA11yProps,
children: (0, _jsxRuntime.jsx)(_reactNativeGestureHandler.GestureDetector, {
gesture: panGesture,
children: (0, _jsxRuntime.jsxs)(_reactNativeReanimated.default.View, {
style: [styles.container, {
height: maxHeight
}, container],
children: [(0, _jsxRuntime.jsx)(_reactNative.View, {
accessibilityActions: closeAccessibilityActions,
accessibilityLabel: closeLabel,
accessibilityRole: closeLabel ? 'button' : undefined,
accessible: closeLabel ? true : undefined,
onAccessibilityAction: onCloseAccessibilityAction,
onAccessibilityTap: closeLabel ? onClose : undefined,
style: [styles.handle, handle]
}), (0, _jsxRuntime.jsx)(_reactNative.View, {
style: [styles.contentContainer, contentContainer],
children: renderContent ? (0, _jsxRuntime.jsx)(_BottomSheetContext.BottomSheetProvider, {
value: bottomSheetModalContextValue,
children: (0, _jsxRuntime.jsx)(_reactNativeReanimated.default.View, {
entering: _reactNativeReanimated.FadeIn.duration(250),
style: styles.sheetContentContainer,
children: enableDynamicSizing ? (0, _jsxRuntime.jsx)(_reactNative.View, {
onLayout: handleDynamicContentLayout,
style: {
paddingBottom: bottomInset
},
children: children
}) : children
})
}) : null
})]
})
})
})]
})
})
});
};
var BottomSheetModal = props => {
if (!props.visible) {
return null;
}
return (0, _jsxRuntime.jsx)(BottomSheetModalInner, {
...props
});
};
exports.BottomSheetModal = BottomSheetModal;
var useStyles = () => {
var _useTheme2 = (0, _ThemeContext.useTheme)(),
semantics = _useTheme2.theme.semantics;
return (0, _react.useMemo)(() => _reactNative.StyleSheet.create({
container: {
borderTopLeftRadius: _theme.primitives.radius4xl,
borderTopRightRadius: _theme.primitives.radius4xl,
backgroundColor: semantics.backgroundCoreElevation1
},
contentContainer: {
flex: 1
},
handle: {
alignSelf: 'center',
borderRadius: 4,
height: 4,
marginVertical: 8,
backgroundColor: '#919191',
width: 32
},
overlay: {
flex: 1,
justifyContent: 'flex-end'
},
backdrop: {
backgroundColor: semantics.backgroundCoreScrim
},
sheetContentContainer: {
flex: 1
}
}), [semantics.backgroundCoreScrim, semantics.backgroundCoreElevation1]);
};
//# sourceMappingURL=BottomSheetModal.js.map