react-native-modal-popover
Version:
Pure JS popover component for react-native
232 lines (208 loc) • 5.03 kB
text/typescript
import { I18nManager } from 'react-native';
export interface Point {
x: number;
y: number;
}
export interface Rect {
x: number;
y: number;
width: number;
height: number;
}
export interface Size {
width: number;
height: number;
}
export type Placement = 'top' | 'end' | 'bottom' | 'start';
export interface Geometry {
origin: Point;
anchor: Point;
placement: Placement;
}
type ComputeGeometry = (
displayArea: Rect,
fromRect: Rect,
contentSize: Size,
arrowSize: Size,
) => Geometry;
export const computeGeometry = (
contentSize: Size,
placement: Placement | 'auto',
fromRect: Rect,
displayArea: Rect,
arrowSize: Size,
): Geometry => {
const effectiveArrowSize = getArrowSize(arrowSize, placement);
switch (placement) {
case 'top':
return computeTopGeometry(
displayArea,
fromRect,
contentSize,
effectiveArrowSize,
);
case 'bottom':
return computeBottomGeometry(
displayArea,
fromRect,
contentSize,
effectiveArrowSize,
);
case 'start':
return computeStartGeometry(
displayArea,
fromRect,
contentSize,
effectiveArrowSize,
);
case 'end':
return computeEndGeometry(
displayArea,
fromRect,
contentSize,
effectiveArrowSize,
);
default:
return computeAutoGeometry(
displayArea,
fromRect,
contentSize,
effectiveArrowSize,
);
}
};
const getArrowSize = (size: Size, placement: Placement | 'auto'): Size => {
if (placement === 'start' || placement === 'end') {
return { width: size.height, height: size.width };
}
return size;
};
const computeTopGeometry: ComputeGeometry = (
displayArea,
fromRect,
contentSize,
arrowSize,
) => {
const origin = {
x: Math.min(
displayArea.x + displayArea.width - contentSize.width,
Math.max(
displayArea.x,
fromRect.x +
(fromRect.width - contentSize.width) / 2 -
(I18nManager.isRTL ? fromRect.width : 0),
),
),
y: fromRect.y - contentSize.height - arrowSize.height,
};
const anchor = {
x: fromRect.x + (fromRect.width / 2) * (I18nManager.isRTL ? -1 : 1),
y: fromRect.y,
};
return { origin, anchor, placement: 'top' };
};
const computeBottomGeometry: ComputeGeometry = (
displayArea,
fromRect,
contentSize,
arrowSize,
) => {
const origin = {
x: Math.min(
displayArea.x + displayArea.width - contentSize.width,
Math.max(
displayArea.x,
fromRect.x +
(fromRect.width - contentSize.width) / 2 -
(I18nManager.isRTL ? fromRect.width : 0),
),
),
y: fromRect.y + fromRect.height + arrowSize.height,
};
const anchor = {
x: fromRect.x + (fromRect.width / 2) * (I18nManager.isRTL ? -1 : 1),
y: fromRect.y + fromRect.height,
};
return { origin, anchor, placement: 'bottom' };
};
const computeStartGeometry: ComputeGeometry = (
displayArea,
fromRect,
contentSize,
arrowSize,
) => {
const origin = {
x:
fromRect.x -
contentSize.width -
arrowSize.width -
(I18nManager.isRTL ? fromRect.width : 0),
y: Math.min(
displayArea.y + displayArea.height - contentSize.height,
Math.max(
displayArea.y,
fromRect.y + fromRect.height - contentSize.height,
),
),
};
const anchor = {
x: fromRect.x - (I18nManager.isRTL ? fromRect.width : 0),
y: fromRect.y + fromRect.height - contentSize.height / 2,
};
return { origin, anchor, placement: 'start' };
};
const computeEndGeometry: ComputeGeometry = (
displayArea,
fromRect,
contentSize,
arrowSize,
) => {
const origin = {
x:
fromRect.x +
fromRect.width +
arrowSize.width -
(I18nManager.isRTL ? fromRect.width : 0),
y: Math.min(
displayArea.y + displayArea.height - contentSize.height,
Math.max(
displayArea.y,
fromRect.y + fromRect.height - contentSize.height,
),
),
};
const anchor = {
x: fromRect.x + (I18nManager.isRTL ? 0 : fromRect.width),
y: fromRect.y + fromRect.height - contentSize.height / 2,
};
return { origin, anchor, placement: 'end' };
};
const computeAutoGeometry = (
displayArea: Rect,
fromRect: Rect,
contentSize: Size,
arrowSize: Size,
): Geometry => {
let geom: Geometry | null = null;
const placements: Placement[] = ['start', 'end', 'top', 'bottom'];
for (let i = 0; i < 4; i += 1) {
const placement = placements[i];
geom = computeGeometry(
contentSize,
placement,
fromRect,
displayArea,
arrowSize,
);
const { origin } = geom;
if (
origin.x >= displayArea.x &&
origin.x <= displayArea.x + displayArea.width - contentSize.width &&
origin.y >= displayArea.y &&
origin.y <= displayArea.y + displayArea.height - contentSize.height
) {
break;
}
}
return geom as Geometry;
};