react-native-modal-popover
Version:
Pure JS popover component for react-native
306 lines • 12.8 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Popover = void 0;
const debounce_1 = __importDefault(require("lodash/debounce"));
const PropTypes = __importStar(require("prop-types"));
const React = __importStar(require("react"));
const react_native_1 = require("react-native");
const PopoverGeometry_1 = require("./PopoverGeometry");
const styles = react_native_1.StyleSheet.create({
container: Object.assign(Object.assign({}, react_native_1.StyleSheet.absoluteFillObject), { opacity: 0, backgroundColor: 'transparent' }),
containerVisible: {
opacity: 1,
},
background: Object.assign(Object.assign({}, react_native_1.StyleSheet.absoluteFillObject), { backgroundColor: 'rgba(0,0,0,0.5)' }),
popover: Object.assign(Object.assign({}, react_native_1.Platform.select({
ios: {
shadowColor: 'black',
shadowOffset: { width: 0, height: 2 },
shadowRadius: 2,
shadowOpacity: 0.4,
backgroundColor: 'transparent',
},
})), { position: 'absolute' }),
content: {
flexDirection: 'column',
position: 'absolute',
backgroundColor: '#f2f2f2',
padding: 8,
},
arrow: {
position: 'absolute',
borderTopColor: '#f2f2f2',
borderRightColor: 'transparent',
borderBottomColor: 'transparent',
borderLeftColor: 'transparent',
},
});
const ARROW_DEG = {
bottom: '-180deg',
start: react_native_1.I18nManager.isRTL ? '90deg' : '-90deg',
end: react_native_1.I18nManager.isRTL ? '-90deg' : '90deg',
top: '0deg',
};
class Popover extends React.PureComponent {
constructor(props) {
super(props);
this.computeGeometry = (props, contentSize) => (0, PopoverGeometry_1.computeGeometry)(contentSize || this.state.contentSize, props.placement, props.fromRect, props.displayArea || this.defaultDisplayArea, props.arrowSize);
this.onOrientationChange = () => {
var _a;
const dimensions = react_native_1.Dimensions.get('window');
this.defaultDisplayArea = {
x: 10,
y: 10,
width: dimensions.width - 20,
height: dimensions.height -
20 -
(this.props.calculateStatusBar ? (_a = react_native_1.StatusBar.currentHeight) !== null && _a !== void 0 ? _a : 0 : 0),
};
};
this.updateState = (0, debounce_1.default)(this.setState, 100);
this.measureContent = ({ nativeEvent: { layout: { width, height }, }, }) => {
if (width && height) {
const contentSize = { width, height };
const geom = this.computeGeometry(this.props, contentSize);
const isAwaitingShow = this.state.isAwaitingShow;
this.updateState(Object.assign(Object.assign({}, geom), { contentSize }), () => {
if (isAwaitingShow) {
this.startAnimation(true);
}
});
}
};
this.getTranslateOrigin = () => {
const { contentSize, origin, anchor } = this.state;
const popoverCenter = {
x: origin.x + contentSize.width / 2,
y: origin.y + contentSize.height / 2,
};
return { x: anchor.x - popoverCenter.x, y: anchor.y - popoverCenter.y };
};
this.startAnimation = (show) => {
const doneCallback = show ? undefined : this.onHidden;
react_native_1.Animated.timing(this.state.animation, {
toValue: show ? 1 : 0,
duration: this.props.duration,
easing: this.props.easing(show),
useNativeDriver: !!this.props.useNativeDriver,
}).start(doneCallback);
};
this.onHidden = () => this.setState({ visible: false, isAwaitingShow: false });
this.computeStyles = () => {
const { animation, anchor, origin } = this.state;
const translateOrigin = this.getTranslateOrigin();
const arrowSize = this.props.arrowSize;
const width = arrowSize.width + 2;
const height = arrowSize.height * 2 + 2;
return {
background: [
styles.background,
this.props.backgroundStyle,
{
opacity: animation.interpolate({
inputRange: [0, 1],
outputRange: [0, 1],
extrapolate: 'clamp',
}),
},
],
arrow: [
styles.arrow,
this.props.arrowStyle,
{
width,
height,
borderTopWidth: height / 2,
left: anchor.x - origin.x - width / 2,
top: anchor.y - origin.y - height / 2,
borderEndWidth: width / 2,
borderBottomWidth: height / 2,
borderStartWidth: width / 2,
transform: [
{
rotate: animation.interpolate({
inputRange: [0, 1],
outputRange: [
ARROW_DEG[this.state.placement],
ARROW_DEG[this.state.placement],
],
extrapolate: 'clamp',
}),
},
{
scale: animation.interpolate({
inputRange: [0, 1],
outputRange: [0, 1],
extrapolate: 'clamp',
}),
},
],
},
],
popover: [
styles.popover,
this.props.popoverStyle,
{ top: origin.y, left: origin.x },
],
content: [
styles.content,
this.props.contentStyle,
{
transform: [
{
translateX: animation.interpolate({
inputRange: [0, 1],
outputRange: [translateOrigin.x, 0],
extrapolate: 'clamp',
}),
},
{
translateY: animation.interpolate({
inputRange: [0, 1],
outputRange: [translateOrigin.y, 0],
extrapolate: 'clamp',
}),
},
{ scale: animation },
],
},
],
};
};
this.state = {
contentSize: { width: 0, height: 0 },
anchor: { x: 0, y: 0 },
origin: { x: 0, y: 0 },
placement: props.placement === 'auto' ? 'top' : props.placement,
visible: false,
isAwaitingShow: false,
animation: new react_native_1.Animated.Value(0),
};
this.onOrientationChange();
}
componentDidMount() {
this.dimensionsSub = react_native_1.Dimensions.addEventListener('change', this.onOrientationChange);
}
componentWillUnmount() {
var _a;
(_a = this.dimensionsSub) === null || _a === void 0 ? void 0 : _a.remove();
}
UNSAFE_componentWillReceiveProps(nextProps) {
const willBeVisible = nextProps.visible;
const { visible, fromRect, displayArea } = this.props;
if (willBeVisible !== visible) {
if (willBeVisible) {
this.setState({
contentSize: { width: 0, height: 0 },
isAwaitingShow: true,
visible: true,
});
}
else {
this.startAnimation(false);
}
}
else if (willBeVisible &&
(fromRect !== nextProps.fromRect || displayArea !== nextProps.displayArea)) {
const geom = this.computeGeometry(nextProps, this.state.contentSize);
const isAwaitingShow = this.state.isAwaitingShow;
this.setState(Object.assign({}, geom), () => {
if (isAwaitingShow) {
this.startAnimation(true);
}
});
}
}
render() {
const { visible } = this.state;
const { onClose, onDismiss, supportedOrientations } = this.props;
const computedStyles = this.computeStyles();
const contentSizeAvailable = this.state.contentSize.width;
return (<react_native_1.Modal transparent visible={visible} onRequestClose={onClose} onDismiss={onDismiss} supportedOrientations={supportedOrientations} onOrientationChange={this.onOrientationChange}>
<react_native_1.View style={[
styles.container,
!!contentSizeAvailable && styles.containerVisible,
]}>
<react_native_1.TouchableWithoutFeedback onPress={this.props.onClose}>
<react_native_1.Animated.View style={computedStyles.background}/>
</react_native_1.TouchableWithoutFeedback>
<react_native_1.Animated.View style={computedStyles.popover}>
<react_native_1.Animated.View onLayout={this.measureContent} style={computedStyles.content}>
{this.props.children}
</react_native_1.Animated.View>
<react_native_1.Animated.View style={computedStyles.arrow}/>
</react_native_1.Animated.View>
</react_native_1.View>
</react_native_1.Modal>);
}
}
exports.Popover = Popover;
Popover.propTypes = {
visible: PropTypes.bool,
onClose: PropTypes.func,
onDismiss: PropTypes.func,
arrowSize: PropTypes.shape({
x: PropTypes.number,
y: PropTypes.number,
}),
placement: PropTypes.oneOf(['left', 'top', 'right', 'bottom', 'auto']),
fromRect: PropTypes.shape({
x: PropTypes.number,
y: PropTypes.number,
width: PropTypes.number,
height: PropTypes.number,
}),
displayArea: PropTypes.shape({
x: PropTypes.number,
y: PropTypes.number,
width: PropTypes.number,
height: PropTypes.number,
}),
backgroundStyle: PropTypes.any,
arrowStyle: PropTypes.any,
popoverStyle: PropTypes.any,
contentStyle: PropTypes.any,
duration: PropTypes.number,
easing: PropTypes.func,
};
Popover.defaultProps = {
visible: false,
onClose: () => { },
onDismiss: () => { },
arrowSize: { width: 16, height: 8 },
placement: 'auto',
duration: 300,
easing: (show) => show ? react_native_1.Easing.out(react_native_1.Easing.back(1.70158)) : react_native_1.Easing.inOut(react_native_1.Easing.quad),
useNativeDriver: false,
calculateStatusBar: false,
};
Popover.displayName = 'Popover';
//# sourceMappingURL=Popover.js.map