react-native-popover-view
Version:
A <Popover /> component for react-native iOS, Android, and Web
424 lines • 22.5 kB
JavaScript
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
import React, { Component } from 'react';
import { Animated, Easing, I18nManager, StyleSheet, TouchableWithoutFeedback, View } from 'react-native';
import Arrow from './Arrow';
import { DEBUG, DEFAULT_ARROW_SIZE, FIX_SHIFT, isWeb, styles } from './Constants';
import { computeGeometry, Geometry } from './Geometry';
import { Placement, Point, Rect, Size } from './Types';
import { getChangedProps, getRectForRef } from './Utility';
var BasePopover = /** @class */ (function (_super) {
__extends(BasePopover, _super);
function BasePopover() {
var _this = _super !== null && _super.apply(this, arguments) || this;
_this.state = {
requestedContentSize: null,
activeGeom: undefined,
nextGeom: undefined,
showing: false,
animatedValues: {
scale: new Animated.Value(0),
translate: new Animated.ValueXY(),
fade: new Animated.Value(0),
translateArrow: new Animated.ValueXY()
}
};
_this._isMounted = false;
_this.animating = false;
_this.animateOutAfterShow = false;
_this.popoverRef = React.createRef();
_this.arrowRef = React.createRef();
return _this;
}
BasePopover.prototype.debug = function (line, obj) {
if (DEBUG || this.props.debug)
console.log("[" + (new Date()).toISOString() + "] " + line + (obj ? ": " + JSON.stringify(obj) : ''));
};
BasePopover.prototype.componentDidMount = function () {
this._isMounted = true;
};
BasePopover.prototype.componentDidUpdate = function (prevProps) {
// Make sure a value we care about has actually changed
var importantProps = ['isVisible', 'fromRect', 'displayArea', 'offset', 'placement'];
var changedProps = getChangedProps(this.props, prevProps, importantProps);
if (!changedProps.length)
return;
this.debug('[BasePopover] componentDidUpdate - changedProps', changedProps);
if (this.props.isVisible !== prevProps.isVisible) {
this.debug("componentDidUpdate - isVisible changed, now " + this.props.isVisible);
if (!this.props.isVisible) {
if (this.state.showing)
this.animateOut();
else
this.animateOutAfterShow = true;
this.debug('componentDidUpdate - Hiding popover');
}
}
else if (this.props.isVisible && prevProps.isVisible) {
this.debug('componentDidUpdate - isVisible not changed, handling other changes');
this.handleChange();
}
};
BasePopover.prototype.componentWillUnmount = function () {
this._isMounted = false;
if (this.state.showing) {
this.debug('componentWillUnmount');
this.animateOut();
}
};
BasePopover.prototype.measureContent = function (requestedContentSize) {
var _this = this;
if (!requestedContentSize.width) {
console.warn("Popover Warning - Can't Show - The Popover content has a width of 0, so there is nothing to present.");
return;
}
if (!requestedContentSize.height) {
console.warn("Popover Warning - Can't Show - The Popover content has a height of 0, so there is nothing to present.");
return;
}
if (this.props.skipMeasureContent()) {
this.debug("measureContent - Skipping, waiting for resize to finish");
return;
}
if (!this.state.requestedContentSize ||
!requestedContentSize.equals(this.state.requestedContentSize)) {
this.debug("measureContent - new requestedContentSize: " + JSON.stringify(requestedContentSize) + " (used to be " + JSON.stringify(this.state.requestedContentSize) + ")");
this.setState({ requestedContentSize: requestedContentSize }, function () { return _this.handleChange(); });
}
else {
this.debug("measureContent - Skipping, content size did not change");
}
};
/*
* Many factors may cause the geometry to change.
* This function collects all of them, waiting for 200ms after the last change,
* then takes action, either bringing up the popover or moving it to its new location
*/
BasePopover.prototype.handleChange = function () {
var _this = this;
if (this.handleChangeTimeout)
clearTimeout(this.handleChangeTimeout);
/*
* This function will be called again once we have a requested content size,
* so safe to ignore for now
*/
if (!this.state.requestedContentSize) {
this.debug('handleChange - no requestedContentSize, exiting...');
return;
}
this.debug('handleChange - waiting 100ms to accumulate all changes');
this.handleChangeTimeout = setTimeout(function () {
var _a = _this.state, activeGeom = _a.activeGeom, animatedValues = _a.animatedValues, requestedContentSize = _a.requestedContentSize;
var _b = _this.props, arrowSize = _b.arrowSize, popoverStyle = _b.popoverStyle, fromRect = _b.fromRect, displayArea = _b.displayArea, placement = _b.placement, onOpenStart = _b.onOpenStart, arrowShift = _b.arrowShift, onPositionChange = _b.onPositionChange, offset = _b.offset, popoverShift = _b.popoverShift;
if (requestedContentSize) {
_this.debug('handleChange - requestedContentSize', requestedContentSize);
_this.debug('handleChange - displayArea', displayArea);
_this.debug('handleChange - fromRect', fromRect);
if (placement)
_this.debug('handleChange - placement', placement.toString());
var geom_1 = computeGeometry({
requestedContentSize: requestedContentSize,
placement: placement,
fromRect: fromRect,
displayArea: displayArea,
arrowSize: arrowSize || DEFAULT_ARROW_SIZE,
popoverStyle: popoverStyle,
arrowShift: arrowShift,
debug: _this.debug.bind(_this),
previousPlacement: _this.getGeom().placement,
offset: offset,
popoverShift: popoverShift
});
_this.setState({ nextGeom: geom_1, requestedContentSize: requestedContentSize }, function () {
if (geom_1.viewLargerThanDisplayArea.width || geom_1.viewLargerThanDisplayArea.height) {
/*
* If the view initially overflowed the display area,
* wait one more render cycle to test-render it within
* the display area to get final calculations for popoverOrigin before show
*/
_this.debug('handleChange - delaying showing popover because viewLargerThanDisplayArea');
}
else if (!activeGeom) {
_this.debug('handleChange - animating in');
if (onOpenStart)
setTimeout(onOpenStart);
_this.animateIn();
}
else if (activeGeom && !Geometry.equals(activeGeom, geom_1)) {
var moveTo = new Point(geom_1.popoverOrigin.x, geom_1.popoverOrigin.y);
_this.debug('handleChange - Triggering popover move to', moveTo);
_this.animateTo({
values: animatedValues,
fade: 1,
scale: 1,
translatePoint: moveTo,
easing: Easing.inOut(Easing.quad),
geom: geom_1,
callback: onPositionChange
});
}
else {
_this.debug('handleChange - no change');
}
});
}
}, 100);
};
BasePopover.getPolarity = function () {
return I18nManager.isRTL ? -1 : 1;
};
BasePopover.prototype.getGeom = function () {
var _a = this.state, activeGeom = _a.activeGeom, nextGeom = _a.nextGeom;
if (activeGeom)
return activeGeom;
if (nextGeom)
return nextGeom;
return new Geometry({
popoverOrigin: new Point(0, 0),
anchorPoint: new Point(0, 0),
placement: Placement.AUTO,
forcedContentSize: new Size(0, 0),
viewLargerThanDisplayArea: {
width: false,
height: false
}
});
};
BasePopover.prototype.getTranslateOrigin = function () {
var requestedContentSize = this.state.requestedContentSize;
var arrowSize = this.props.arrowSize || DEFAULT_ARROW_SIZE;
var _a = this.getGeom(), forcedContentSize = _a.forcedContentSize, viewLargerThanDisplayArea = _a.viewLargerThanDisplayArea, anchorPoint = _a.anchorPoint, placement = _a.placement;
var viewWidth = 0;
if (viewLargerThanDisplayArea.width && (forcedContentSize === null || forcedContentSize === void 0 ? void 0 : forcedContentSize.width))
viewWidth = forcedContentSize.width;
else if (requestedContentSize === null || requestedContentSize === void 0 ? void 0 : requestedContentSize.width)
viewWidth = requestedContentSize.width;
if ([Placement.LEFT, Placement.RIGHT].includes(placement))
viewWidth += arrowSize.height;
var viewHeight = 0;
if (viewLargerThanDisplayArea.height && (forcedContentSize === null || forcedContentSize === void 0 ? void 0 : forcedContentSize.height))
viewHeight = forcedContentSize.height;
else if (requestedContentSize === null || requestedContentSize === void 0 ? void 0 : requestedContentSize.height)
viewHeight = requestedContentSize.height;
if ([Placement.TOP, Placement.BOTTOM].includes(placement))
viewHeight += arrowSize.height;
this.debug('getTranslateOrigin - popoverSize', { width: viewWidth, height: viewHeight });
this.debug('getTranslateOrigin - anchorPoint', anchorPoint);
return new Point(anchorPoint.x - (viewWidth / 2), anchorPoint.y - (viewHeight / 2));
};
BasePopover.prototype.animateOut = function () {
var _this = this;
if (this.props.onCloseStart)
setTimeout(this.props.onCloseStart);
if (this._isMounted)
this.setState({ showing: false });
this.debug('animateOut - isMounted', this._isMounted);
this.animateTo({
values: this.state.animatedValues,
fade: 0,
scale: 0,
translatePoint: this.getTranslateOrigin(),
callback: function () { return setTimeout(_this.props.onCloseComplete); },
easing: Easing.inOut(Easing.quad),
geom: this.getGeom()
});
};
BasePopover.prototype.animateIn = function () {
var _this = this;
var nextGeom = this.state.nextGeom;
if (nextGeom !== undefined && nextGeom instanceof Geometry) {
var values = this.state.animatedValues;
// Should grow from anchor point
var translateStart = this.getTranslateOrigin();
// eslint-disable-next-line
translateStart.y += FIX_SHIFT; // Temp fix for useNativeDriver issue
values.translate.setValue(translateStart);
var translatePoint = new Point(nextGeom.popoverOrigin.x, nextGeom.popoverOrigin.y);
this.debug('animateIn - translateStart', translateStart);
this.debug('animateIn - translatePoint', translatePoint);
this.animateTo({
values: values,
fade: 1,
scale: 1,
translatePoint: translatePoint,
easing: Easing.out(Easing.back(1)),
geom: nextGeom,
callback: function () {
if (_this._isMounted) {
_this.setState({ showing: true });
if (_this.props.debug || DEBUG) {
setTimeout(function () {
return _this.popoverRef.current &&
getRectForRef(_this.popoverRef).then(function (rect) { return _this.debug('animateIn - onOpenComplete - Calculated Popover Rect', rect); });
});
setTimeout(function () {
return _this.arrowRef.current &&
getRectForRef(_this.arrowRef).then(function (rect) { return _this.debug('animateIn - onOpenComplete - Calculated Arrow Rect', rect); });
});
}
}
if (_this.props.onOpenComplete)
setTimeout(_this.props.onOpenComplete);
if (_this.animateOutAfterShow || !_this._isMounted) {
_this.animateOut();
_this.animateOutAfterShow = false;
}
}
});
}
};
BasePopover.prototype.animateTo = function (args) {
var _this = this;
var fade = args.fade, translatePoint = args.translatePoint, scale = args.scale, callback = args.callback, easing = args.easing, values = args.values;
var commonConfig = __assign({ duration: 300, easing: easing, useNativeDriver: !isWeb }, this.props.animationConfig);
if (this.animating) {
setTimeout(function () { return _this.animateTo(args); }, 100);
return;
}
// eslint-disable-next-line
translatePoint.y = translatePoint.y + FIX_SHIFT; // Temp fix for useNativeDriver issue
if (!fade && fade !== 0) {
console.log('Popover: Fade value is null');
return;
}
if (!translatePoint) {
console.log('Popover: Translate Point value is null');
return;
}
if (!scale && scale !== 0) {
console.log('Popover: Scale value is null');
return;
}
this.animating = true;
Animated.parallel([
Animated.timing(values.fade, __assign(__assign({}, commonConfig), { toValue: fade })),
Animated.timing(values.translate, __assign(__assign({}, commonConfig), { toValue: translatePoint })),
Animated.timing(values.scale, __assign(__assign({}, commonConfig), { toValue: scale }))
]).start(function () {
_this.animating = false;
if (_this._isMounted)
_this.setState({ activeGeom: _this.state.nextGeom });
if (callback)
callback();
});
};
BasePopover.prototype.render = function () {
var _this = this;
var _a = this.state, animatedValues = _a.animatedValues, nextGeom = _a.nextGeom, requestedContentSize = _a.requestedContentSize;
var flattenedPopoverStyle = StyleSheet.flatten(this.props.popoverStyle);
var arrowSize = this.props.arrowSize || DEFAULT_ARROW_SIZE;
var geom = this.getGeom();
var transformStyle = __assign(__assign({ position: 'absolute' }, requestedContentSize), { transform: [
{ translateX: animatedValues.translate.x },
{ translateY: animatedValues.translate.y },
{ scale: animatedValues.scale }
] });
var shadowOffset = flattenedPopoverStyle.shadowOffset, shadowColor = flattenedPopoverStyle.shadowColor, shadowOpacity = flattenedPopoverStyle.shadowOpacity, shadowRadius = flattenedPopoverStyle.shadowRadius, elevation = flattenedPopoverStyle.elevation, otherPopoverStyles = __rest(flattenedPopoverStyle, ["shadowOffset", "shadowColor", "shadowOpacity", "shadowRadius", "elevation"]);
var shadowStyle = {
shadowOffset: shadowOffset,
shadowColor: shadowColor,
shadowOpacity: shadowOpacity,
shadowRadius: shadowRadius
};
var contentWrapperStyle = __assign(__assign(__assign({}, styles.popoverContent), otherPopoverStyles), { elevation: elevation });
/*
* We want to always use next here, because the we need this to re-render
* before we can animate to the correct spot for the active.
*/
if (nextGeom) {
contentWrapperStyle.maxWidth = nextGeom.forcedContentSize.width;
contentWrapperStyle.maxHeight = nextGeom.forcedContentSize.height;
}
var arrowPositionStyle = {};
if (geom.placement === Placement.RIGHT || geom.placement === Placement.LEFT) {
arrowPositionStyle.top = geom.anchorPoint.y - geom.popoverOrigin.y - arrowSize.height;
if (transformStyle.width)
transformStyle.width += arrowSize.height;
if (geom.placement === Placement.RIGHT)
contentWrapperStyle.left = arrowSize.height;
}
else if (geom.placement === Placement.TOP || geom.placement === Placement.BOTTOM) {
arrowPositionStyle.left = geom.anchorPoint.x - geom.popoverOrigin.x - (arrowSize.width / 2);
if (transformStyle.height)
transformStyle.height += arrowSize.height;
if (geom.placement === Placement.BOTTOM)
contentWrapperStyle.top = arrowSize.height;
}
switch (geom.placement) {
case Placement.TOP:
arrowPositionStyle.bottom = 0;
break;
case Placement.BOTTOM:
arrowPositionStyle.top = 0;
break;
case Placement.LEFT:
arrowPositionStyle.right = 0;
break;
case Placement.RIGHT:
arrowPositionStyle.left = 0;
break;
default:
}
// Temp fix for useNativeDriver issue
var backgroundShift = animatedValues.fade.interpolate({
inputRange: [0, 0.0001, 1],
outputRange: [0, FIX_SHIFT, FIX_SHIFT]
});
var backgroundStyle = __assign(__assign(__assign({}, styles.background), { transform: [{ translateY: backgroundShift }] }), StyleSheet.flatten(this.props.backgroundStyle));
var containerStyle = __assign(__assign({}, styles.container), { opacity: animatedValues.fade });
var backgroundColor = flattenedPopoverStyle.backgroundColor ||
styles.popoverContent.backgroundColor;
return (React.createElement(View, { pointerEvents: "box-none", accessibilityViewIsModal: true, importantForAccessibility: "yes", style: [styles.container, { top: -1 * FIX_SHIFT }] },
React.createElement(View, { pointerEvents: "box-none", style: [styles.container, { top: FIX_SHIFT, flex: 1 }], onLayout: function (evt) { return _this.props.onDisplayAreaChanged(new Rect(evt.nativeEvent.layout.x, evt.nativeEvent.layout.y - FIX_SHIFT, evt.nativeEvent.layout.width, evt.nativeEvent.layout.height)); } }),
React.createElement(Animated.View, { pointerEvents: "box-none", style: containerStyle, onAccessibilityEscape: this.props.onRequestClose },
this.props.showBackground !== false && (React.createElement(TouchableWithoutFeedback, { onPress: this.props.onRequestClose, accessibilityRole: "button", accessible: true },
React.createElement(Animated.View, { style: backgroundStyle }))),
React.createElement(View, { pointerEvents: "box-none", style: __assign({ top: 0, left: 0 }, shadowStyle) },
React.createElement(Animated.View, { style: transformStyle },
React.createElement(View, { ref: this.popoverRef, style: contentWrapperStyle, onLayout: function (evt) {
var layout = __assign({}, evt.nativeEvent.layout);
setTimeout(function () { return _this._isMounted &&
_this.measureContent(new Size(layout.width, layout.height)); }, 10);
} }, this.props.children),
geom.placement !== Placement.FLOATING &&
React.createElement(Arrow, { ref: this.arrowRef, placement: geom.placement, color: backgroundColor, arrowSize: arrowSize, positionStyle: arrowPositionStyle, elevation: elevation }))))));
};
return BasePopover;
}(Component));
export default BasePopover;
//# sourceMappingURL=BasePopover.js.map