UNPKG

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
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