react-native-popover-view
Version:
A <Popover /> component for react-native iOS, Android, and Web
366 lines • 21.4 kB
JavaScript
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);
};
import { Rect, Size, Point, Placement } from './Types';
import { getBorderRadius } from './Utility';
import { POPOVER_MARGIN } from './Constants';
var Geometry = /** @class */ (function () {
function Geometry(_a) {
var popoverOrigin = _a.popoverOrigin, anchorPoint = _a.anchorPoint, placement = _a.placement, forcedContentSize = _a.forcedContentSize, viewLargerThanDisplayArea = _a.viewLargerThanDisplayArea;
this.popoverOrigin = popoverOrigin;
this.anchorPoint = anchorPoint;
this.placement = placement;
this.forcedContentSize = forcedContentSize;
this.viewLargerThanDisplayArea = viewLargerThanDisplayArea;
}
Geometry.equals = function (a, b) {
var _a, _b, _c, _d;
return a.popoverOrigin.equals(b.popoverOrigin) &&
a.anchorPoint.equals(b.anchorPoint) &&
a.placement === b.placement &&
a.forcedContentSize.equals(b.forcedContentSize) &&
((_a = a.viewLargerThanDisplayArea) === null || _a === void 0 ? void 0 : _a.width) === ((_b = b.viewLargerThanDisplayArea) === null || _b === void 0 ? void 0 : _b.width) &&
((_c = a.viewLargerThanDisplayArea) === null || _c === void 0 ? void 0 : _c.height) === ((_d = b.viewLargerThanDisplayArea) === null || _d === void 0 ? void 0 : _d.height);
};
return Geometry;
}());
export { Geometry };
export function computeGeometry(options) {
var requestedContentSize = options.requestedContentSize, placement = options.placement, displayArea = options.displayArea, debug = options.debug, popoverStyle = options.popoverStyle, arrowShift = options.arrowShift, popoverShift = options.popoverShift, arrowSize = options.arrowSize;
var newGeom = null;
// Make copy so doesn't modify original
var fromRect = options.fromRect
? Rect.clone(options.fromRect)
: null;
if (fromRect && options.fromRect instanceof Rect) {
var borderRadius = getBorderRadius(popoverStyle);
// Default to first option if given list of placements
var selectedPlacement = Array.isArray(placement) ? placement[0] : placement;
// If we can find a placement in the list that is better, use that
if (Array.isArray(placement)) {
var spaceList = generateSpaceList({ fromRect: fromRect, displayArea: displayArea, requestedContentSize: requestedContentSize, arrowSize: arrowSize });
var bestPlacements_1 = calculateBestPlacements(spaceList);
var bestProvidedPlacement = placement.
filter(function (p) { return p === Placement.AUTO || p === Placement.FLOATING || bestPlacements_1.includes(p); })[0];
if (bestProvidedPlacement)
selectedPlacement = bestProvidedPlacement;
}
switch (selectedPlacement) {
case Placement.TOP:
newGeom = computeTopGeometry(__assign(__assign({}, options), { fromRect: fromRect, borderRadius: borderRadius }));
break;
case Placement.BOTTOM:
newGeom = computeBottomGeometry(__assign(__assign({}, options), { fromRect: fromRect, borderRadius: borderRadius }));
break;
case Placement.LEFT:
newGeom = computeLeftGeometry(__assign(__assign({}, options), { fromRect: fromRect, borderRadius: borderRadius }));
break;
case Placement.RIGHT:
newGeom = computeRightGeometry(__assign(__assign({}, options), { fromRect: fromRect, borderRadius: borderRadius }));
break;
case Placement.FLOATING:
newGeom = null;
break;
default:
newGeom = computeAutoGeometry(__assign(__assign({}, options), { fromRect: fromRect, borderRadius: borderRadius }));
}
debug('computeGeometry - initial chosen geometry', newGeom);
/*
* If the popover will be restricted and the view that the popover is showing
* from is sufficiently large, try to show the popover inside the view
*/
if (newGeom &&
(newGeom.viewLargerThanDisplayArea.width || newGeom.viewLargerThanDisplayArea.height)) {
var fromRectHeightVisible = fromRect.y < displayArea.y
? fromRect.height - (displayArea.y - fromRect.y)
: displayArea.y + displayArea.height - fromRect.y;
if (fromRect.width > requestedContentSize.width &&
fromRectHeightVisible > requestedContentSize.height) {
var preferredX = Math.max(fromRect.x + 10, fromRect.x + ((fromRect.width - requestedContentSize.width) / 2));
var preferredY = Math.max(fromRect.y + 10, fromRect.y + ((fromRect.height - requestedContentSize.height) / 2));
var constrainedX = Math.max(preferredX, displayArea.x);
if (constrainedX + requestedContentSize.width > displayArea.x + displayArea.width)
constrainedX = displayArea.x + displayArea.width - requestedContentSize.width;
var constrainedY = Math.max(preferredY, displayArea.y);
if (constrainedY + requestedContentSize.height > displayArea.y + displayArea.height)
constrainedY = displayArea.y + displayArea.height - requestedContentSize.height;
var forcedContentSize = new Size(Math.min(fromRect.width - 20, displayArea.width), Math.min(fromRect.height - 20, displayArea.height));
debug('computeGeometry - showing inside anchor');
newGeom = new Geometry({
popoverOrigin: new Point(constrainedX, constrainedY),
anchorPoint: new Point(fromRect.x + (fromRect.width / 2), fromRect.y + (fromRect.height / 2)),
placement: Placement.FLOATING,
forcedContentSize: forcedContentSize,
viewLargerThanDisplayArea: {
width: requestedContentSize.width > forcedContentSize.width,
height: requestedContentSize.height > forcedContentSize.height
}
});
}
else if (
/*
* If we can't fit inside or outside the fromRect, show the popover floating on the screen,
* but only do this if they haven't asked for a specifc placement type
* and if it will actually help show more content
*/
placement === Placement.AUTO &&
((newGeom.viewLargerThanDisplayArea.width &&
[Placement.RIGHT, Placement.LEFT].includes(newGeom.placement)) ||
(newGeom.viewLargerThanDisplayArea.height &&
[Placement.TOP, Placement.BOTTOM].includes(newGeom.placement)))) {
newGeom = null;
}
}
}
if (!newGeom) {
var minY = displayArea.y;
var minX = displayArea.x;
var preferedY = ((displayArea.height - requestedContentSize.height) / 2) + displayArea.y;
var preferedX = ((displayArea.width - requestedContentSize.width) / 2) + displayArea.x;
debug('computeGeometry - showing floating');
newGeom = new Geometry({
popoverOrigin: new Point(Math.max(minX, preferedX), Math.max(minY, preferedY)),
anchorPoint: new Point((displayArea.width / 2) + displayArea.x, (displayArea.height / 2) + displayArea.y),
placement: Placement.FLOATING,
forcedContentSize: new Size(displayArea.width, displayArea.height),
viewLargerThanDisplayArea: {
width: preferedX < minX - 1,
height: preferedY < minY - 1
}
});
// Apply popover shift
if (!newGeom.viewLargerThanDisplayArea.width && (popoverShift === null || popoverShift === void 0 ? void 0 : popoverShift.x)) {
debug('computeGeometry - applying popoverShift.x', popoverShift.x);
var horizontalMargin = (displayArea.width - requestedContentSize.width) / 2;
newGeom.popoverOrigin.x += popoverShift.x * horizontalMargin;
newGeom.anchorPoint.x = newGeom.popoverOrigin.x + (requestedContentSize.width / 2);
}
if (!newGeom.viewLargerThanDisplayArea.height && (popoverShift === null || popoverShift === void 0 ? void 0 : popoverShift.y)) {
debug('computeGeometry - applying popoverShift.y', popoverShift.y);
var verticalMargin = (displayArea.height - requestedContentSize.height) / 2;
newGeom.popoverOrigin.y += popoverShift.y * verticalMargin;
newGeom.anchorPoint.y = newGeom.popoverOrigin.y + (requestedContentSize.height / 2);
}
}
if (arrowShift && fromRect) {
if (newGeom.placement === Placement.BOTTOM || newGeom.placement === Placement.TOP)
newGeom.anchorPoint.x += arrowShift * 0.5 * fromRect.width;
else
newGeom.anchorPoint.y += arrowShift * 0.5 * fromRect.height;
}
debug('computeGeometry - final chosen geometry', newGeom);
return newGeom;
}
function computeTopGeometry(_a) {
var displayArea = _a.displayArea, fromRect = _a.fromRect, requestedContentSize = _a.requestedContentSize, arrowSize = _a.arrowSize, borderRadius = _a.borderRadius, offset = _a.offset;
// Apply a margin on non-arrow sides
displayArea = new Rect(displayArea.x + POPOVER_MARGIN, displayArea.y + POPOVER_MARGIN, displayArea.width - (POPOVER_MARGIN * 2), displayArea.height);
if (offset)
fromRect.y -= offset;
var minY = displayArea.y;
var maxY = displayArea.y + displayArea.height;
var preferredY = fromRect.y - requestedContentSize.height - arrowSize.height;
var forcedContentSize = new Size(displayArea.width, (fromRect.y - arrowSize.height - displayArea.y));
var viewLargerThanDisplayArea = {
height: preferredY <= minY - 1,
width: requestedContentSize.width >= displayArea.width + 1
};
var viewWidth = viewLargerThanDisplayArea.width
? forcedContentSize.width
: requestedContentSize.width;
var maxX = displayArea.x + displayArea.width - viewWidth;
var minX = displayArea.x;
var preferredX = fromRect.x + ((fromRect.width - viewWidth) / 2);
var popoverOrigin = new Point(Math.min(maxX, Math.max(minX, preferredX)), Math.min(maxY, Math.max(minY, preferredY)));
var anchorPoint = new Point(fromRect.x + (fromRect.width / 2), fromRect.y);
// Make sure the arrow isn't cut off
anchorPoint.x = Math.max(anchorPoint.x, popoverOrigin.x + (arrowSize.width / 2) + borderRadius);
anchorPoint.x = Math.min(anchorPoint.x, displayArea.x + displayArea.width - (arrowSize.width / 2) - borderRadius);
return new Geometry({
popoverOrigin: popoverOrigin,
anchorPoint: anchorPoint,
placement: Placement.TOP,
forcedContentSize: forcedContentSize,
viewLargerThanDisplayArea: viewLargerThanDisplayArea
});
}
function computeBottomGeometry(_a) {
var displayArea = _a.displayArea, fromRect = _a.fromRect, requestedContentSize = _a.requestedContentSize, arrowSize = _a.arrowSize, borderRadius = _a.borderRadius, offset = _a.offset;
// Apply a margin on non-arrow sides
displayArea = new Rect(displayArea.x + POPOVER_MARGIN, displayArea.y, displayArea.width - (POPOVER_MARGIN * 2), displayArea.height - POPOVER_MARGIN);
if (offset)
fromRect.y += offset;
var minY = displayArea.y;
var maxY = displayArea.y + displayArea.height;
var preferedY = fromRect.y + fromRect.height;
var forcedContentSize = new Size(displayArea.width, displayArea.y + displayArea.height - preferedY);
var viewLargerThanDisplayArea = {
height: preferedY + requestedContentSize.height >= displayArea.y + displayArea.height + 1,
width: requestedContentSize.width >= displayArea.width + 1
};
var viewWidth = viewLargerThanDisplayArea.width
? forcedContentSize.width
: requestedContentSize.width;
var maxX = displayArea.x + displayArea.width - viewWidth;
var minX = displayArea.x;
var preferedX = fromRect.x + ((fromRect.width - viewWidth) / 2);
var popoverOrigin = new Point(Math.min(maxX, Math.max(minX, preferedX)), Math.min(maxY, Math.max(minY, preferedY)));
var anchorPoint = new Point(fromRect.x + (fromRect.width / 2), fromRect.y + fromRect.height);
// Make sure the arrow isn't cut off
anchorPoint.x = Math.max(anchorPoint.x, popoverOrigin.x + (arrowSize.width / 2) + borderRadius);
anchorPoint.x = Math.min(anchorPoint.x, displayArea.x + displayArea.width - (arrowSize.width / 2) - borderRadius);
return new Geometry({
popoverOrigin: popoverOrigin,
anchorPoint: anchorPoint,
placement: Placement.BOTTOM,
forcedContentSize: forcedContentSize,
viewLargerThanDisplayArea: viewLargerThanDisplayArea
});
}
function computeLeftGeometry(_a) {
var displayArea = _a.displayArea, fromRect = _a.fromRect, requestedContentSize = _a.requestedContentSize, borderRadius = _a.borderRadius, arrowSize = _a.arrowSize, offset = _a.offset;
// Apply a margin on non-arrow sides
displayArea = new Rect(displayArea.x + POPOVER_MARGIN, displayArea.y + POPOVER_MARGIN, displayArea.width, displayArea.height - (POPOVER_MARGIN * 2));
if (offset)
fromRect.x -= offset;
var forcedContentSize = new Size(fromRect.x - displayArea.x - arrowSize.width, displayArea.height);
var viewLargerThanDisplayArea = {
height: requestedContentSize.height >= displayArea.height + 1,
width: requestedContentSize.width >= fromRect.x - displayArea.x - arrowSize.width + 1
};
var viewWidth = viewLargerThanDisplayArea.width
? forcedContentSize.width
: requestedContentSize.width;
var viewHeight = viewLargerThanDisplayArea.height
? forcedContentSize.height
: requestedContentSize.height;
var preferedX = fromRect.x - viewWidth - arrowSize.height;
var minX = displayArea.x;
var maxX = displayArea.x + displayArea.width;
var preferedY = fromRect.y + ((fromRect.height - viewHeight) / 2);
var minY = displayArea.y;
var maxY = (displayArea.height - viewHeight) + displayArea.y;
var popoverOrigin = new Point(Math.min(Math.max(minX, preferedX), maxX), Math.min(Math.max(minY, preferedY), maxY));
var anchorPoint = new Point(fromRect.x, fromRect.y + (fromRect.height / 2));
// Make sure the arrow isn't cut off
anchorPoint.y = Math.max(anchorPoint.y, popoverOrigin.y + (arrowSize.height / 2) + borderRadius);
anchorPoint.y = Math.min(anchorPoint.y, displayArea.y + displayArea.height - (arrowSize.height / 2) - borderRadius);
return new Geometry({
popoverOrigin: popoverOrigin,
anchorPoint: anchorPoint,
placement: Placement.LEFT,
forcedContentSize: forcedContentSize,
viewLargerThanDisplayArea: viewLargerThanDisplayArea
});
}
function computeRightGeometry(_a) {
var displayArea = _a.displayArea, fromRect = _a.fromRect, requestedContentSize = _a.requestedContentSize, arrowSize = _a.arrowSize, borderRadius = _a.borderRadius, offset = _a.offset;
// Apply a margin on non-arrow sides
displayArea = new Rect(displayArea.x, displayArea.y + POPOVER_MARGIN, displayArea.width - POPOVER_MARGIN, displayArea.height - (POPOVER_MARGIN * 2));
if (offset)
fromRect.x += offset;
var horizontalSpace = displayArea.x + displayArea.width - (fromRect.x + fromRect.width) - arrowSize.width;
var forcedContentSize = new Size(horizontalSpace, displayArea.height);
var viewLargerThanDisplayArea = {
height: requestedContentSize.height >= displayArea.height + 1,
width: requestedContentSize.width >= horizontalSpace + 1
};
var viewHeight = viewLargerThanDisplayArea.height
? forcedContentSize.height
: requestedContentSize.height;
var preferedX = fromRect.x + fromRect.width;
var minX = displayArea.x;
var maxX = displayArea.x + displayArea.width;
var preferedY = fromRect.y + ((fromRect.height - viewHeight) / 2);
var minY = displayArea.y;
var maxY = (displayArea.height - viewHeight) + displayArea.y;
var popoverOrigin = new Point(Math.min(Math.max(minX, preferedX), maxX), Math.min(Math.max(minY, preferedY), maxY));
var anchorPoint = new Point(fromRect.x + fromRect.width, fromRect.y + (fromRect.height / 2.0));
// Make sure the arrow isn't cut off
anchorPoint.y = Math.max(anchorPoint.y, popoverOrigin.y + (arrowSize.height / 2) + borderRadius);
anchorPoint.y = Math.min(anchorPoint.y, displayArea.y + displayArea.height - (arrowSize.height / 2) - borderRadius);
return new Geometry({
popoverOrigin: popoverOrigin,
anchorPoint: anchorPoint,
placement: Placement.RIGHT,
forcedContentSize: forcedContentSize,
viewLargerThanDisplayArea: viewLargerThanDisplayArea
});
}
function generateSpaceList(_a) {
var _b;
var fromRect = _a.fromRect, displayArea = _a.displayArea, arrowSize = _a.arrowSize, requestedContentSize = _a.requestedContentSize;
function generateOption(props) {
return __assign(__assign({}, props), { fits: props.sizeAvailable >= props.sizeRequested, extraSpace: props.sizeAvailable - props.sizeRequested });
}
return _b = {},
_b[Placement.LEFT] = generateOption({
sizeAvailable: fromRect.x - displayArea.x - arrowSize.width,
sizeRequested: requestedContentSize.width
}),
_b[Placement.RIGHT] = generateOption({
sizeAvailable: displayArea.x + displayArea.width - (fromRect.x + fromRect.width) - arrowSize.width,
sizeRequested: requestedContentSize.width
}),
_b[Placement.TOP] = generateOption({
sizeAvailable: fromRect.y - displayArea.y - arrowSize.width,
sizeRequested: requestedContentSize.height
}),
_b[Placement.BOTTOM] = generateOption({
sizeAvailable: displayArea.y + displayArea.height - (fromRect.y + fromRect.height) - arrowSize.width,
sizeRequested: requestedContentSize.height
}),
_b;
}
function computeAutoGeometry(options) {
var displayArea = options.displayArea, requestedContentSize = options.requestedContentSize, fromRect = options.fromRect, previousPlacement = options.previousPlacement, debug = options.debug, arrowSize = options.arrowSize;
// Keep same placement if possible (left/right)
if (previousPlacement === Placement.LEFT || previousPlacement === Placement.RIGHT) {
var geom = previousPlacement === Placement.LEFT
? computeLeftGeometry(options)
: computeRightGeometry(options);
debug('computeAutoGeometry - Left/right tryping to keep same, geometry', geom);
if (!geom.viewLargerThanDisplayArea.width)
return geom;
}
// Keep same placement if possible (top/bottom)
if (previousPlacement === Placement.TOP || previousPlacement === Placement.BOTTOM) {
var geom = previousPlacement === Placement.TOP
? computeTopGeometry(options)
: computeBottomGeometry(options);
debug('computeAutoGeometry - Top/bottom tryping to keep same, geometry', geom);
if (!geom.viewLargerThanDisplayArea.height)
return geom;
}
/*
* Otherwise, find the place that can fit it best (try left/right but
* default to top/bottom as that will typically have more space)
*/
// generating list of all possible sides with validity
debug('computeAutoGeometry - displayArea', displayArea);
debug('computeAutoGeometry - fromRect', fromRect);
var spaceList = generateSpaceList({ fromRect: fromRect, displayArea: displayArea, arrowSize: arrowSize, requestedContentSize: requestedContentSize });
debug('computeAutoGeometry - List of available space', spaceList);
var bestPlacementPosition = calculateBestPlacements(spaceList)[0];
debug('computeAutoGeometry - Found best postition for placement', bestPlacementPosition);
switch (bestPlacementPosition) {
case Placement.LEFT: return computeLeftGeometry(options);
case Placement.RIGHT: return computeRightGeometry(options);
case Placement.BOTTOM: return computeBottomGeometry(options);
case Placement.TOP: return computeTopGeometry(options);
// Return nothing so popover will be placed in middle of screen
default: return null;
}
}
function calculateBestPlacements(spaceList) {
return Object.keys(spaceList).filter(function (o) { var _a; return (_a = spaceList[o]) === null || _a === void 0 ? void 0 : _a.fits; }).sort(function (a, b) { var _a, _b, _c, _d; return ((_b = (_a = spaceList[b]) === null || _a === void 0 ? void 0 : _a.extraSpace) !== null && _b !== void 0 ? _b : 0) - ((_d = (_c = spaceList[a]) === null || _c === void 0 ? void 0 : _c.extraSpace) !== null && _d !== void 0 ? _d : 0); });
}
//# sourceMappingURL=Geometry.js.map