UNPKG

react-native-popover-view

Version:

A <Popover /> component for react-native iOS, Android, and Web

354 lines 20.1 kB
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 __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; 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 { Dimensions, Keyboard, Platform, StatusBar } from 'react-native'; import { DEBUG, DEFAULT_STATUS_BAR_TRANSLUCENT, isIOS } from './Constants'; import { Rect } from './Types'; import { getChangedProps, getRectForRef } from './Utility'; import BasePopover from './BasePopover'; var AdaptivePopover = /** @class */ (function (_super) { __extends(AdaptivePopover, _super); function AdaptivePopover(props) { var _this = _super.call(this, props) || this; _this.state = { fromRect: null, shiftedDisplayArea: null, defaultDisplayArea: null, displayAreaOffset: null, showing: false }; /* * This is used so that when the device is rotating * or the viewport is expanding for any other reason, * we can suspend updates due to content changes until * we are finished calculating the new display * area and rect for the new viewport size. * This makes the recalc on rotation much faster. */ _this.waitForResizeToFinish = false; _this.skipNextDefaultDisplayArea = false; _this._isMounted = false; _this.keyboardDidHideSubscription = null; _this.keyboardDidShowSubscription = null; _this.handleResizeEventSubscription = null; _this.handleResizeEvent = _this.handleResizeEvent.bind(_this); _this.keyboardDidHide = _this.keyboardDidHide.bind(_this); _this.keyboardDidShow = _this.keyboardDidShow.bind(_this); return _this; } AdaptivePopover.prototype.getUnshiftedDisplayArea = function () { return this.props.displayArea || this.state.defaultDisplayArea || new Rect(0, 0, Dimensions.get('window').width, Dimensions.get('window').height); }; // Apply insets and shifts if needed AdaptivePopover.prototype.getDisplayArea = function () { var _a, _b, _c, _d, _e, _f; var displayAreaInsets = this.props.displayAreaInsets; var displayArea = this.state.shiftedDisplayArea || this.getUnshiftedDisplayArea(); if (displayAreaInsets) { this.debug('[AdaptivePopover] getDisplayArea - displayAreaInsets', displayAreaInsets); return new Rect(displayArea.x + ((_a = displayAreaInsets.left) !== null && _a !== void 0 ? _a : 0), displayArea.y + ((_b = displayAreaInsets.top) !== null && _b !== void 0 ? _b : 0), displayArea.width - ((_c = displayAreaInsets.left) !== null && _c !== void 0 ? _c : 0) - ((_d = displayAreaInsets.right) !== null && _d !== void 0 ? _d : 0), displayArea.height - ((_e = displayAreaInsets.top) !== null && _e !== void 0 ? _e : 0) - ((_f = displayAreaInsets.bottom) !== null && _f !== void 0 ? _f : 0)); } return displayArea; }; AdaptivePopover.prototype.componentDidMount = function () { this.handleResizeEventSubscription = Dimensions.addEventListener('change', this.handleResizeEvent); if (this.props.fromRect) this.setState({ fromRect: this.props.fromRect }); else if (this.props.fromRef) this.calculateRectFromRef(); this._isMounted = true; }; AdaptivePopover.prototype.componentWillUnmount = function () { var _a, _b, _c, _d; this._isMounted = false; if (typeof ((_a = this.handleResizeEventSubscription) === null || _a === void 0 ? void 0 : _a.remove) === 'function') (_b = this.handleResizeEventSubscription) === null || _b === void 0 ? void 0 : _b.remove(); else // Backward-compatibility with RN <= 0.63 Dimensions.removeEventListener('change', this.handleResizeEvent); (_c = this.keyboardDidShowSubscription) === null || _c === void 0 ? void 0 : _c.remove(); (_d = this.keyboardDidHideSubscription) === null || _d === void 0 ? void 0 : _d.remove(); }; AdaptivePopover.prototype.componentDidUpdate = function (prevProps) { // Make sure a value we care about has actually changed var importantProps = ['fromRef', 'fromRect', 'displayArea']; var changedProps = getChangedProps(this.props, prevProps, importantProps); if (!changedProps.length) return; this.debug('[AdaptivePopover] componentDidUpdate - changedProps', changedProps); if (changedProps.includes('fromRect')) { this.debug('componentDidUpdate - fromRect changed', this.props.fromRect); this.setState({ fromRect: this.props.fromRect || null }); } else if (this.props.fromRef !== prevProps.fromRef) { this.debug('componentDidUpdate - fromRef changed'); if (this.props.fromRef) this.calculateRectFromRef(); else this.setState({ fromRect: null }); } if (this.props.isVisible && prevProps.isVisible) { if (changedProps.includes('displayArea') || (this.displayAreaStore && !this.getDisplayArea().equals(this.displayAreaStore))) { this.debug('componentDidUpdate - displayArea changed', this.getDisplayArea()); this.displayAreaStore = this.getDisplayArea(); } } }; // First thing called when device rotates AdaptivePopover.prototype.handleResizeEvent = function (change) { this.debug('handleResizeEvent - New Dimensions', change); if (this.props.isVisible) { this.waitForResizeToFinish = true; } }; AdaptivePopover.prototype.debug = function (line, obj) { if (DEBUG || this.props.debug) console.log("[" + (new Date()).toISOString() + "] " + line + (obj ? ": " + JSON.stringify(obj) : '')); }; AdaptivePopover.prototype.setDefaultDisplayArea = function (newDisplayArea) { return __awaiter(this, void 0, void 0, function () { var defaultDisplayArea, isValidDisplayArea, displayAreaOffset_1; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: if (!this._isMounted) return [2 /*return*/]; defaultDisplayArea = this.state.defaultDisplayArea; isValidDisplayArea = newDisplayArea.width > 0 && newDisplayArea.height > 0; if (!((!defaultDisplayArea || !newDisplayArea.equals(defaultDisplayArea)) && isValidDisplayArea)) return [3 /*break*/, 6]; this.debug('setDefaultDisplayArea - newDisplayArea', newDisplayArea); if (!!this.skipNextDefaultDisplayArea) return [3 /*break*/, 5]; return [4 /*yield*/, this.props.getDisplayAreaOffset()]; case 1: displayAreaOffset_1 = _a.sent(); this.debug('setDefaultDisplayArea - displayAreaOffset', displayAreaOffset_1); return [4 /*yield*/, new Promise(function (resolve) { _this.setState({ defaultDisplayArea: newDisplayArea, displayAreaOffset: displayAreaOffset_1 }, function () { return resolve(null); }); })]; case 2: _a.sent(); if (!this.props.fromRef) return [3 /*break*/, 4]; return [4 /*yield*/, this.calculateRectFromRef()]; case 3: _a.sent(); _a.label = 4; case 4: this.waitForResizeToFinish = false; this.displayAreaStore = this.getDisplayArea(); _a.label = 5; case 5: if (this.skipNextDefaultDisplayArea) this.debug('setDefaultDisplayArea - Skipping first because isLandscape'); this.skipNextDefaultDisplayArea = false; _a.label = 6; case 6: return [2 /*return*/]; } }); }); }; // Custom type here, as KeyboardEvent type does not contain endCoordinates AdaptivePopover.prototype.keyboardDidShow = function (e) { this.debug("keyboardDidShow - keyboard height: " + e.endCoordinates.height); this.shiftForKeyboard(e.endCoordinates.height); }; AdaptivePopover.prototype.keyboardDidHide = function () { this.debug('keyboardDidHide'); if (this._isMounted) this.setState({ shiftedDisplayArea: null }); }; AdaptivePopover.prototype.shiftForKeyboard = function (keyboardHeight) { var displayArea = this.getUnshiftedDisplayArea(); var absoluteVerticalCutoff = Dimensions.get('window').height - keyboardHeight - (isIOS ? 10 : 40); var combinedY = Math.min(displayArea.height + displayArea.y, absoluteVerticalCutoff); this.setState({ shiftedDisplayArea: new Rect(displayArea.x, displayArea.y, displayArea.width, combinedY - displayArea.y) }); }; AdaptivePopover.prototype.calculateRectFromRef = function () { var _a, _b, _c; return __awaiter(this, void 0, void 0, function () { var fromRef, initialRect, displayAreaOffset, count, shouldAdjustForAndroidStatusBar, verticalOffsetAndroidAdjustment, verticalOffset, horizontalOffset, rect; return __generator(this, function (_d) { switch (_d.label) { case 0: fromRef = this.props.fromRef; initialRect = this.state.fromRect || new Rect(0, 0, 0, 0); displayAreaOffset = (_a = this.state.displayAreaOffset) !== null && _a !== void 0 ? _a : { x: 0, y: 0 }; this.debug('calculateRectFromRef - waiting for ref'); count = 0; _d.label = 1; case 1: if (!!(fromRef === null || fromRef === void 0 ? void 0 : fromRef.current)) return [3 /*break*/, 3]; return [4 /*yield*/, new Promise(function (resolve) { setTimeout(resolve, 100); })]; case 2: _d.sent(); // Timeout after 2 seconds if (count++ > 20) return [2 /*return*/]; return [3 /*break*/, 1]; case 3: shouldAdjustForAndroidStatusBar = Platform.OS === 'android' && ((_b = this.props.statusBarTranslucent) !== null && _b !== void 0 ? _b : DEFAULT_STATUS_BAR_TRANSLUCENT); verticalOffsetAndroidAdjustment = shouldAdjustForAndroidStatusBar ? ((_c = StatusBar.currentHeight) !== null && _c !== void 0 ? _c : 0) : 0; verticalOffset = verticalOffsetAndroidAdjustment - displayAreaOffset.y; horizontalOffset = -displayAreaOffset.x; this.debug('calculateRectFromRef - waiting for ref to move from', initialRect); count = 0; _d.label = 4; case 4: return [4 /*yield*/, getRectForRef(fromRef)]; case 5: rect = _d.sent(); if ([rect.x, rect.y, rect.width, rect.height].every(function (i) { return i === undefined; })) { this.debug('calculateRectFromRef - rect not found, all properties undefined'); return [2 /*return*/]; } rect = new Rect(rect.x + horizontalOffset, rect.y + verticalOffset, rect.width, rect.height); if (count === 0 && AdaptivePopover.hasRetrievedSatisfyingRect(rect, initialRect)) { return [3 /*break*/, 8]; } return [4 /*yield*/, new Promise(function (resolve) { setTimeout(resolve, 100); })]; case 6: _d.sent(); // Timeout after 2 seconds if (count++ > 20) return [2 /*return*/]; _d.label = 7; case 7: if (!AdaptivePopover.hasRetrievedSatisfyingRect(rect, initialRect)) return [3 /*break*/, 4]; _d.label = 8; case 8: this.debug('calculateRectFromRef - calculated Rect', rect); if (this._isMounted) this.setState({ fromRect: rect }); return [2 /*return*/]; } }); }); }; AdaptivePopover.prototype.render = function () { var _this = this; var _a = this.props, onOpenStart = _a.onOpenStart, onCloseStart = _a.onCloseStart, onCloseComplete = _a.onCloseComplete, fromRef = _a.fromRef, otherProps = __rest(_a, ["onOpenStart", "onCloseStart", "onCloseComplete", "fromRef"]); var _b = this.state, fromRect = _b.fromRect, showing = _b.showing; // Don't render popover until we have an initial fromRect calculated for the view if (fromRef && !fromRect && !showing) return null; return (React.createElement(BasePopover, __assign({}, otherProps, { displayArea: this.getDisplayArea(), fromRect: fromRect, onOpenStart: function () { onOpenStart === null || onOpenStart === void 0 ? void 0 : onOpenStart(); _this.debug('Setting up keyboard listeners'); _this.keyboardDidShowSubscription = Keyboard.addListener('keyboardDidShow', _this.keyboardDidShow); _this.keyboardDidHideSubscription = Keyboard.addListener('keyboardDidHide', _this.keyboardDidHide); _this.displayAreaStore = _this.getDisplayArea(); _this.setState({ showing: true }); }, onCloseStart: function () { onCloseStart === null || onCloseStart === void 0 ? void 0 : onCloseStart(); _this.debug('Tearing down keyboard listeners'); if (_this.keyboardDidShowSubscription !== null) { _this.keyboardDidShowSubscription.remove(); _this.keyboardDidShowSubscription = null; } if (_this.keyboardDidHideSubscription !== null) { _this.keyboardDidHideSubscription.remove(); _this.keyboardDidHideSubscription = null; } if (_this._isMounted) _this.setState({ shiftedDisplayArea: null }); }, onCloseComplete: function () { _this.setState({ showing: false }, function () { onCloseComplete === null || onCloseComplete === void 0 ? void 0 : onCloseComplete(); }); }, skipMeasureContent: function () { return _this.waitForResizeToFinish; }, onDisplayAreaChanged: function (rect) { return _this.setDefaultDisplayArea(rect); } }))); }; AdaptivePopover.hasRetrievedSatisfyingRect = function (rect, initialRect) { /* * Checking if x and y is less than -1000 because of a strange issue on Android related * to the "Toggle from" feature, where the rect.y is a large negative number at first */ return !(rect.equals(initialRect) || rect.y < -1000 || rect.x < -1000); }; return AdaptivePopover; }(Component)); export default AdaptivePopover; //# sourceMappingURL=AdaptivePopover.js.map