UNPKG

@wordpress/components

Version:
467 lines (417 loc) 14.1 kB
import _extends from "@babel/runtime/helpers/esm/extends"; import { createElement, Fragment } from "@wordpress/element"; /** * External dependencies */ import { Text, View, Platform, PanResponder, Dimensions, Keyboard, StatusBar, ScrollView, TouchableHighlight } from 'react-native'; import Modal from 'react-native-modal'; import SafeArea from 'react-native-safe-area'; /** * WordPress dependencies */ import { subscribeAndroidModalClosed } from '@wordpress/react-native-bridge'; import { Component } from '@wordpress/element'; import { withPreferredColorScheme } from '@wordpress/compose'; /** * Internal dependencies */ import styles from './styles.scss'; import Button from './button'; import Cell from './cell'; import CyclePickerCell from './cycle-picker-cell'; import PickerCell from './picker-cell'; import SwitchCell from './switch-cell'; import RangeCell from './range-cell'; import ColorCell from './color-cell'; import LinkCell from './link-cell'; import LinkSuggestionItemCell from './link-suggestion-item-cell'; import RadioCell from './radio-cell'; import NavigationScreen from './bottom-sheet-navigation/navigation-screen'; import NavigationContainer from './bottom-sheet-navigation/navigation-container'; import KeyboardAvoidingView from './keyboard-avoiding-view'; import BottomSheetSubSheet from './sub-sheet'; import NavigationHeader from './navigation-header'; import { BottomSheetProvider } from './bottom-sheet-context'; class BottomSheet extends Component { constructor() { super(...arguments); this.onSafeAreaInsetsUpdate = this.onSafeAreaInsetsUpdate.bind(this); this.onScroll = this.onScroll.bind(this); this.isScrolling = this.isScrolling.bind(this); this.onShouldEnableScroll = this.onShouldEnableScroll.bind(this); this.onDismiss = this.onDismiss.bind(this); this.onShouldSetBottomSheetMaxHeight = this.onShouldSetBottomSheetMaxHeight.bind(this); this.setIsFullScreen = this.setIsFullScreen.bind(this); this.onDimensionsChange = this.onDimensionsChange.bind(this); this.onCloseBottomSheet = this.onCloseBottomSheet.bind(this); this.onHandleClosingBottomSheet = this.onHandleClosingBottomSheet.bind(this); this.onHardwareButtonPress = this.onHardwareButtonPress.bind(this); this.onHandleHardwareButtonPress = this.onHandleHardwareButtonPress.bind(this); this.keyboardWillShow = this.keyboardWillShow.bind(this); this.keyboardDidHide = this.keyboardDidHide.bind(this); this.state = { safeAreaBottomInset: 0, safeAreaTopInset: 0, bounces: false, maxHeight: 0, keyboardHeight: 0, scrollEnabled: true, isScrolling: false, handleClosingBottomSheet: null, handleHardwareButtonPress: null, isMaxHeightSet: true, isFullScreen: this.props.isFullScreen || false }; SafeArea.getSafeAreaInsetsForRootView().then(this.onSafeAreaInsetsUpdate); Dimensions.addEventListener('change', this.onDimensionsChange); } keyboardWillShow(e) { const { height } = e.endCoordinates; this.setState({ keyboardHeight: height }, () => this.onSetMaxHeight()); } keyboardDidHide() { this.setState({ keyboardHeight: 0 }, () => this.onSetMaxHeight()); } componentDidMount() { if (Platform.OS === 'android') { this.androidModalClosedSubscription = subscribeAndroidModalClosed(() => { this.props.onClose(); }); } this.keyboardWillShowListener = Keyboard.addListener('keyboardWillShow', this.keyboardWillShow); this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this.keyboardDidHide); this.safeAreaEventSubscription = SafeArea.addEventListener('safeAreaInsetsForRootViewDidChange', this.onSafeAreaInsetsUpdate); this.onSetMaxHeight(); } componentWillUnmount() { this.keyboardWillShowListener.remove(); this.keyboardDidHideListener.remove(); if (this.androidModalClosedSubscription) { this.androidModalClosedSubscription.remove(); } if (this.safeAreaEventSubscription === null) { return; } this.safeAreaEventSubscription.remove(); this.safeAreaEventSubscription = null; SafeArea.removeEventListener('safeAreaInsetsForRootViewDidChange', this.onSafeAreaInsetsUpdate); } onSafeAreaInsetsUpdate(result) { const { safeAreaBottomInset, safeAreaTopInset } = this.state; if (this.safeAreaEventSubscription === null) { return; } const { safeAreaInsets } = result; if (safeAreaBottomInset !== safeAreaInsets.bottom || safeAreaTopInset !== safeAreaInsets.top) { this.setState({ safeAreaBottomInset: safeAreaInsets.bottom, safeAreaTopInset: safeAreaInsets.top }); } } onSetMaxHeight() { const { height, width } = Dimensions.get('window'); const { safeAreaBottomInset, keyboardHeight } = this.state; const statusBarHeight = Platform.OS === 'android' ? StatusBar.currentHeight : 0; // `maxHeight` when modal is opened along with a keyboard const maxHeightWithOpenKeyboard = 0.95 * (Dimensions.get('window').height - keyboardHeight - statusBarHeight); // On horizontal mode `maxHeight` has to be set on 90% of width if (width > height) { this.setState({ maxHeight: Math.min(0.9 * height, maxHeightWithOpenKeyboard) }); // On vertical mode `maxHeight` has to be set on 50% of width } else { this.setState({ maxHeight: Math.min(height / 2 - safeAreaBottomInset, maxHeightWithOpenKeyboard) }); } } onDimensionsChange() { this.onSetMaxHeight(); this.setState({ bounces: false }); } isCloseToBottom({ layoutMeasurement, contentOffset, contentSize }) { return layoutMeasurement.height + contentOffset.y >= contentSize.height - contentOffset.y; } isCloseToTop({ contentOffset }) { return contentOffset.y < 10; } onScroll({ nativeEvent }) { if (this.isCloseToTop(nativeEvent)) { this.setState({ bounces: false }); } else if (this.isCloseToBottom(nativeEvent)) { this.setState({ bounces: true }); } } onDismiss() { const { onDismiss } = this.props; if (onDismiss) { onDismiss(); } this.onCloseBottomSheet(); } onShouldEnableScroll(value) { this.setState({ scrollEnabled: value }); } onShouldSetBottomSheetMaxHeight(value) { this.setState({ isMaxHeightSet: value }); } isScrolling(value) { this.setState({ isScrolling: value }); } onHandleClosingBottomSheet(action) { this.setState({ handleClosingBottomSheet: action }); } onHandleHardwareButtonPress(action) { this.setState({ handleHardwareButtonPress: action }); } onCloseBottomSheet() { const { onClose } = this.props; const { handleClosingBottomSheet } = this.state; if (handleClosingBottomSheet) { handleClosingBottomSheet(); this.onHandleClosingBottomSheet(null); } if (onClose) { onClose(); } this.onShouldSetBottomSheetMaxHeight(true); } setIsFullScreen(isFullScreen) { if (isFullScreen !== this.state.isFullScreen) { if (isFullScreen) { this.setState({ isFullScreen, isMaxHeightSet: false }); } else { this.setState({ isFullScreen, isMaxHeightSet: true }); } } } onHardwareButtonPress() { const { onClose } = this.props; const { handleHardwareButtonPress } = this.state; if (handleHardwareButtonPress && handleHardwareButtonPress()) { return; } if (onClose) { return onClose(); } } getContentStyle() { const { safeAreaBottomInset } = this.state; return { paddingBottom: (safeAreaBottomInset || 0) + styles.scrollableContent.paddingBottom }; } render() { const { title = '', isVisible, leftButton, rightButton, hideHeader, style = {}, contentStyle = {}, getStylesFromColorScheme, children, withHeaderSeparator = false, hasNavigation, ...rest } = this.props; const { maxHeight, bounces, safeAreaBottomInset, safeAreaTopInset, isScrolling, scrollEnabled, isMaxHeightSet, isFullScreen } = this.state; const panResponder = PanResponder.create({ onMoveShouldSetPanResponder: (evt, gestureState) => { // 'swiping-to-close' option is temporarily and partially disabled // on Android ( swipe / drag is still available in the top most area - near drag indicator) if (Platform.OS === 'ios') { // Activates swipe down over child Touchables if the swipe is long enough. // With this we can adjust sensibility on the swipe vs tap gestures. if (gestureState.dy > 3 && !bounces) { gestureState.dy = 0; return true; } } return false; } }); const backgroundStyle = getStylesFromColorScheme(styles.background, styles.backgroundDark); const bottomSheetHeaderTitleStyle = getStylesFromColorScheme(styles.bottomSheetHeaderTitle, styles.bottomSheetHeaderTitleDark); let listStyle = {}; if (isFullScreen) { listStyle = { flexGrow: 1 }; } else if (isMaxHeightSet) { listStyle = { maxHeight }; // Allow setting a "static" height of the bottom sheet // by settting the min height to the max height. if (this.props.setMinHeightToMaxHeight) { listStyle.minHeight = maxHeight; } } const listProps = { disableScrollViewPanResponder: true, bounces, onScroll: this.onScroll, onScrollBeginDrag: this.onScrollBeginDrag, onScrollEndDrag: this.onScrollEndDrag, scrollEventThrottle: 16, contentContainerStyle: [styles.content, hideHeader && styles.emptyHeader, contentStyle, isFullScreen && { flexGrow: 1 }], style: listStyle, safeAreaBottomInset, scrollEnabled, automaticallyAdjustContentInsets: false }; const WrapperView = hasNavigation ? View : ScrollView; const getHeader = () => createElement(Fragment, null, createElement(View, { style: styles.bottomSheetHeader }, createElement(View, { style: styles.flex }, leftButton), createElement(Text, { style: bottomSheetHeaderTitleStyle, maxFontSizeMultiplier: 3 }, title), createElement(View, { style: styles.flex }, rightButton)), withHeaderSeparator && createElement(View, { style: styles.separator })); return createElement(Modal, _extends({ isVisible: isVisible, style: styles.bottomModal, animationInTiming: 400, animationOutTiming: 300, backdropTransitionInTiming: 50, backdropTransitionOutTiming: 50, backdropOpacity: 0.2, onBackdropPress: this.onCloseBottomSheet, onBackButtonPress: this.onHardwareButtonPress, onSwipe: this.onCloseBottomSheet, onDismiss: Platform.OS === 'ios' ? this.onDismiss : undefined, onModalHide: Platform.OS === 'android' ? this.onDismiss : undefined, swipeDirection: "down", onMoveShouldSetResponder: scrollEnabled && panResponder.panHandlers.onMoveShouldSetResponder, onMoveShouldSetResponderCapture: scrollEnabled && panResponder.panHandlers.onMoveShouldSetResponderCapture, onAccessibilityEscape: this.onCloseBottomSheet }, rest), createElement(KeyboardAvoidingView, { behavior: Platform.OS === 'ios' && 'padding', style: { ...backgroundStyle, borderColor: 'rgba(0, 0, 0, 0.1)', marginTop: Platform.OS === 'ios' && isFullScreen ? safeAreaTopInset : 0, flex: isFullScreen ? 1 : undefined, ...(Platform.OS === 'android' && isFullScreen ? styles.backgroundFullScreen : {}), ...style }, keyboardVerticalOffset: -safeAreaBottomInset }, !(Platform.OS === 'android' && isFullScreen) && createElement(View, { style: styles.dragIndicator }), !hideHeader && getHeader(), createElement(WrapperView, hasNavigation ? { style: listProps.style } : listProps, createElement(BottomSheetProvider, { value: { shouldEnableBottomSheetScroll: this.onShouldEnableScroll, shouldEnableBottomSheetMaxHeight: this.onShouldSetBottomSheetMaxHeight, isBottomSheetContentScrolling: isScrolling, onHandleClosingBottomSheet: this.onHandleClosingBottomSheet, onHandleHardwareButtonPress: this.onHandleHardwareButtonPress, listProps, setIsFullScreen: this.setIsFullScreen, safeAreaBottomInset } }, hasNavigation ? createElement(Fragment, null, children) : createElement(TouchableHighlight, { accessible: false }, createElement(Fragment, null, children))), !hasNavigation && createElement(View, { style: { height: safeAreaBottomInset || styles.scrollableContent.paddingBottom } })))); } } function getWidth() { return Math.min(Dimensions.get('window').width, styles.background.maxWidth); } const ThemedBottomSheet = withPreferredColorScheme(BottomSheet); ThemedBottomSheet.getWidth = getWidth; ThemedBottomSheet.Button = Button; ThemedBottomSheet.Cell = Cell; ThemedBottomSheet.SubSheet = BottomSheetSubSheet; ThemedBottomSheet.NavigationHeader = NavigationHeader; ThemedBottomSheet.CyclePickerCell = CyclePickerCell; ThemedBottomSheet.PickerCell = PickerCell; ThemedBottomSheet.SwitchCell = SwitchCell; ThemedBottomSheet.RangeCell = RangeCell; ThemedBottomSheet.ColorCell = ColorCell; ThemedBottomSheet.LinkCell = LinkCell; ThemedBottomSheet.LinkSuggestionItemCell = LinkSuggestionItemCell; ThemedBottomSheet.RadioCell = RadioCell; ThemedBottomSheet.NavigationScreen = NavigationScreen; ThemedBottomSheet.NavigationContainer = NavigationContainer; export default ThemedBottomSheet; //# sourceMappingURL=index.native.js.map