@gluestack-ui/core
Version:
Universal UI components for React Native, Expo, and Next.js
283 lines • 11.8 kB
JavaScript
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());
});
};
import React from 'react';
import { Dimensions, } from 'react-native';
import { isRTL } from '@gluestack-ui/utils/aria';
import { APPROX_STATUSBAR_HEIGHT } from './utils';
const measureOffset = (ref) => new Promise((resolve) => {
if (ref.current) {
ref.current.measureInWindow((x, y, width, height) => {
resolve({ top: y, left: x, width, height });
});
}
});
const getArrowPropsWithStatusBarHeight = ({ top, left, }) => {
let topWithStatusBarHeight = top;
if (typeof top !== 'undefined' &&
typeof APPROX_STATUSBAR_HEIGHT !== 'undefined') {
topWithStatusBarHeight = top + APPROX_STATUSBAR_HEIGHT;
}
else {
topWithStatusBarHeight = undefined;
}
return {
style: {
left: left,
top: topWithStatusBarHeight,
},
};
};
export function useOverlayPosition(props) {
var _a, _b, _c;
let { targetRef, overlayRef, placement = 'bottom', offset = 0, crossOffset = 0, isOpen = true, shouldFlip = true, shouldOverlapWithTrigger = false, } = props;
let [position, setPosition] = React.useState({
position: {},
arrowOffsetLeft: undefined,
arrowOffsetTop: undefined,
maxHeight: undefined,
placement: undefined,
});
let [rendered, setRendered] = React.useState(false);
let updatePosition = () => __awaiter(this, void 0, void 0, function* () {
const [overlayOffset, triggerOffset] = yield Promise.all([
measureOffset(overlayRef),
measureOffset(targetRef),
]);
if (!overlayOffset.width ||
!overlayOffset.height ||
!triggerOffset.width ||
!triggerOffset.height) {
requestAnimationFrame(updatePosition);
return;
}
const { height: windowHeight, width: windowWidth } = Dimensions.get('window');
const positions = calculatePosition({
placement: translateRTL(placement),
targetNode: triggerOffset,
overlayNode: overlayOffset,
scrollNode: overlayOffset,
padding: 0,
shouldFlip,
boundaryElement: {
top: 0,
left: 0,
width: windowWidth,
height: windowHeight,
},
offset,
crossOffset,
shouldOverlapWithTrigger,
});
setPosition(positions);
setRendered(true);
});
React.useEffect(() => {
return () => {
setRendered(false);
};
}, []);
React.useLayoutEffect(() => {
updatePosition();
}, [
placement,
isOpen,
offset,
shouldFlip,
crossOffset,
shouldOverlapWithTrigger,
]);
const style = Object.assign({}, position.position);
if (((_a = position === null || position === void 0 ? void 0 : position.position) === null || _a === void 0 ? void 0 : _a.top) || ((_b = position === null || position === void 0 ? void 0 : position.position) === null || _b === void 0 ? void 0 : _b.top) === 0) {
style.top = (((_c = position === null || position === void 0 ? void 0 : position.position) === null || _c === void 0 ? void 0 : _c.top) || 0) + (APPROX_STATUSBAR_HEIGHT || 0);
}
const arrowPropsWithStatusBarHeight = getArrowPropsWithStatusBarHeight({
left: position === null || position === void 0 ? void 0 : position.arrowOffsetLeft,
top: position === null || position === void 0 ? void 0 : position.arrowOffsetTop,
});
const returnProps = {
rendered,
overlayProps: {
style,
},
placement: position.placement,
arrowProps: arrowPropsWithStatusBarHeight,
updatePosition,
isFlipped: position.isFlipped,
};
if (position.maxHeight !== undefined) {
returnProps.overlayProps.style.maxHeight = position.maxHeight;
}
return returnProps;
}
function translateRTL(position) {
if (isRTL()) {
return position.replace('start', 'right').replace('end', 'left');
}
return position.replace('start', 'left').replace('end', 'right');
}
const calculatePosition = (opts) => {
let { placement, targetNode, overlayNode, scrollNode, padding, shouldFlip, boundaryElement, offset, crossOffset, shouldOverlapWithTrigger, } = opts;
let childOffset = targetNode;
let isContainerPositioned = false;
let overlaySize = overlayNode;
let margins = { top: 0, bottom: 0, left: 0, right: 0 };
let scrollSize = scrollNode;
let boundaryDimensions = boundaryElement;
let containerOffsetWithBoundary = overlayNode;
return calculatePositionInternal(placement, childOffset, overlaySize, scrollSize, margins, padding, shouldFlip, boundaryDimensions, containerOffsetWithBoundary, offset, crossOffset, isContainerPositioned, shouldOverlapWithTrigger);
};
function calculatePositionInternal(placementInput, childOffset, overlaySize, scrollSize, margins, padding, flip, boundaryDimensions, containerOffsetWithBoundary, offset, crossOffset, isContainerPositioned, shouldOverlapWithTrigger) {
let placementInfo = parsePlacement(placementInput);
let { size, crossAxis, crossSize, placement, crossPlacement } = placementInfo;
let position = computePosition(childOffset, boundaryDimensions, overlaySize, placementInfo, offset, crossOffset, containerOffsetWithBoundary, isContainerPositioned);
let normalizedOffset = offset;
let isFlipped = false;
let space = getAvailableSpace(boundaryDimensions, containerOffsetWithBoundary, childOffset, margins, padding + offset, placementInfo);
if (flip && scrollSize[size] > space) {
let flippedPlacementInfo = parsePlacement(`${FLIPPED_DIRECTION[placement]} ${crossPlacement}`);
let flippedPosition = computePosition(childOffset, boundaryDimensions, overlaySize, flippedPlacementInfo, offset, crossOffset, containerOffsetWithBoundary, isContainerPositioned);
let flippedSpace = getAvailableSpace(boundaryDimensions, containerOffsetWithBoundary, childOffset, margins, padding + offset, flippedPlacementInfo);
if (flippedSpace > space) {
isFlipped = true;
placementInfo = flippedPlacementInfo;
position = flippedPosition;
normalizedOffset = offset;
}
else {
isFlipped = false;
}
}
else {
isFlipped = false;
}
let delta = getDelta(crossAxis, position[crossAxis], overlaySize[crossSize], boundaryDimensions, padding);
position[crossAxis] += delta;
let maxHeight = getMaxHeight(position, boundaryDimensions, containerOffsetWithBoundary, childOffset, margins, padding);
overlaySize.height = Math.min(overlaySize.height, maxHeight);
position = computePosition(childOffset, boundaryDimensions, overlaySize, placementInfo, normalizedOffset, crossOffset, containerOffsetWithBoundary, isContainerPositioned);
delta = getDelta(crossAxis, position[crossAxis], overlaySize[crossSize], boundaryDimensions, padding);
position[crossAxis] += delta;
let arrowPosition = {};
arrowPosition[crossAxis] =
childOffset[crossAxis] - position[crossAxis] + childOffset[crossSize] / 2;
if (shouldOverlapWithTrigger) {
position[FLIPPED_DIRECTION[placementInfo.placement]] =
position[FLIPPED_DIRECTION[placementInfo.placement]] - childOffset[size];
}
return {
position,
maxHeight,
arrowOffsetLeft: arrowPosition.left,
arrowOffsetTop: arrowPosition.top,
placement: placementInfo.placement,
isFlipped,
};
}
function getDelta(axis, offset, size, containerDimensions, padding) {
let containerScroll = containerDimensions[axis];
let containerHeight = containerDimensions[AXIS_SIZE[axis]];
let startEdgeOffset = offset - padding - containerScroll;
let endEdgeOffset = offset + padding - containerScroll + size;
if (startEdgeOffset < 0) {
return -startEdgeOffset;
}
else if (endEdgeOffset > containerHeight) {
return Math.max(containerHeight - endEdgeOffset, -startEdgeOffset);
}
else {
return 0;
}
}
function getMaxHeight(position, boundaryDimensions, _containerOffsetWithBoundary, childOffset, _margins, _padding) {
return position.top != null
?
Math.max(0, boundaryDimensions.height -
position.top)
:
Math.max(0, childOffset.top -
0);
}
function computePosition(childOffset, boundaryDimensions, overlaySize, placementInfo, offset, crossOffset, _containerOffsetWithBoundary, _isContainerPositioned) {
let { placement, crossPlacement, axis, crossAxis, size, crossSize } = placementInfo;
let position = {};
position[crossAxis] = childOffset[crossAxis];
if (crossPlacement === 'center') {
position[crossAxis] +=
(childOffset[crossSize] - overlaySize[crossSize]) / 2;
}
else if (crossPlacement !== crossAxis) {
position[crossAxis] += childOffset[crossSize] - overlaySize[crossSize];
}
position[crossAxis] += crossOffset;
let minViablePosition = childOffset[crossAxis] +
childOffset[crossSize] / 2 -
overlaySize[crossSize];
let maxViablePosition = childOffset[crossAxis] + childOffset[crossSize] / 2;
position[crossAxis] = Math.min(Math.max(minViablePosition, position[crossAxis]), maxViablePosition);
if (placement === axis) {
const containerHeight = boundaryDimensions[size];
position[FLIPPED_DIRECTION[axis]] = Math.floor(containerHeight - childOffset[axis] + offset);
}
else {
position[axis] = Math.floor(childOffset[axis] + childOffset[size] + offset);
}
return position;
}
function getAvailableSpace(boundaryDimensions, _containerOffsetWithBoundary, childOffset, _margins, padding, placementInfo) {
let { placement, axis, size } = placementInfo;
if (placement === axis) {
return Math.max(0, childOffset[axis] - padding);
}
return Math.max(0, boundaryDimensions[size] - childOffset[axis] - childOffset[size] - padding);
}
const AXIS = {
top: 'top',
bottom: 'top',
left: 'left',
right: 'left',
};
const FLIPPED_DIRECTION = {
top: 'bottom',
bottom: 'top',
left: 'right',
right: 'left',
};
const CROSS_AXIS = {
top: 'left',
left: 'top',
};
const AXIS_SIZE = {
top: 'height',
left: 'width',
};
const PARSED_PLACEMENT_CACHE = {};
function parsePlacement(input) {
if (PARSED_PLACEMENT_CACHE[input]) {
return PARSED_PLACEMENT_CACHE[input];
}
let [placement, crossPlacement] = input.split(' ');
let axis = AXIS[placement] || 'right';
let crossAxis = CROSS_AXIS[axis];
if (!AXIS[crossPlacement]) {
crossPlacement = 'center';
}
let size = AXIS_SIZE[axis];
let crossSize = AXIS_SIZE[crossAxis];
PARSED_PLACEMENT_CACHE[input] = {
placement,
crossPlacement,
axis,
crossAxis,
size,
crossSize,
};
return PARSED_PLACEMENT_CACHE[input];
}
//# sourceMappingURL=useOverlayPosition.js.map