UNPKG

@gluestack-ui/core

Version:

Universal UI components for React Native, Expo, and Next.js

283 lines 11.8 kB
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