react-native-paper
Version:
Material design for React Native
130 lines (109 loc) • 3.58 kB
text/typescript
import { Dimensions, LayoutRectangle, ViewStyle } from 'react-native';
type ChildrenMeasurement = {
width: number;
height: number;
pageX: number;
pageY: number;
};
type TooltipLayout = LayoutRectangle;
export type Measurement = {
children: ChildrenMeasurement;
tooltip: TooltipLayout;
measured: boolean;
};
/**
* Return true when the tooltip center x-coordinate relative to the wrapped element is negative.
* The tooltip will be placed at the starting x-coordinate from the wrapped element.
*/
const overflowLeft = (center: number): boolean => {
return center < 0;
};
/**
* Return true when the tooltip center x-coordinate + tooltip width is greater than the layout width
* The tooltip width will grow from right to left relative to the wrapped element.
*/
const overflowRight = (center: number, tooltipWidth: number): boolean => {
const { width: layoutWidth } = Dimensions.get('window');
return center + tooltipWidth > layoutWidth;
};
/**
* Return true when the children y-coordinate + its height + tooltip height is greater than the layout height.
* The tooltip will be placed at the top of the wrapped element.
*/
const overflowBottom = (
childrenY: number,
childrenHeight: number,
tooltipHeight: number
): boolean => {
const { height: layoutHeight } = Dimensions.get('window');
return childrenY + childrenHeight + tooltipHeight > layoutHeight;
};
const getTooltipXPosition = (
{ pageX: childrenX, width: childrenWidth }: ChildrenMeasurement,
{ width: tooltipWidth }: TooltipLayout
): number => {
// when the children use position absolute the childrenWidth is measured as 0,
// so it's best to anchor the tooltip at the start of the children
const center =
childrenWidth > 0
? childrenX + (childrenWidth - tooltipWidth) / 2
: childrenX;
if (overflowLeft(center)) return childrenX;
if (overflowRight(center, tooltipWidth))
return childrenX + childrenWidth - tooltipWidth;
return center;
};
const getTooltipYPosition = (
{ pageY: childrenY, height: childrenHeight }: ChildrenMeasurement,
{ height: tooltipHeight }: TooltipLayout
): number => {
if (overflowBottom(childrenY, childrenHeight, tooltipHeight))
return childrenY - tooltipHeight;
return childrenY + childrenHeight;
};
const getChildrenMeasures = (
style: ViewStyle | Array<ViewStyle>,
measures: ChildrenMeasurement
): ChildrenMeasurement => {
const { position, top, bottom, left, right } = Array.isArray(style)
? style.reduce((acc, current) => ({ ...acc, ...current }))
: style;
if (position === 'absolute') {
let pageX = 0;
let pageY = measures.pageY;
let height = 0;
let width = 0;
if (typeof left === 'number') {
pageX = left;
width = 0;
}
if (typeof right === 'number') {
pageX = measures.width - right;
width = 0;
}
if (typeof top === 'number') {
pageY = pageY + top;
}
if (typeof bottom === 'number') {
pageY = pageY - bottom;
}
return { pageX, pageY, width, height };
}
return measures;
};
export const getTooltipPosition = (
{ children, tooltip, measured }: Measurement,
component: React.ReactElement<{
style: ViewStyle | Array<ViewStyle> | undefined | null;
}>
): {} | { left: number; top: number } => {
if (!measured) return {};
let measures = children;
if (component.props.style) {
measures = getChildrenMeasures(component.props.style, children);
}
return {
left: getTooltipXPosition(measures, tooltip),
top: getTooltipYPosition(measures, tooltip),
};
};