@gluestack-ui/core
Version:
Universal UI components for React Native, Expo, and Next.js
256 lines • 11.1 kB
JavaScript
import getCss from 'dom-helpers/css';
import getOffset from 'dom-helpers/offset';
import getPosition from 'dom-helpers/position';
import getScrollLeft from 'dom-helpers/scrollLeft';
import getScrollTop from 'dom-helpers/scrollTop';
import ownerDocument from 'dom-helpers/ownerDocument';
import getComputedStyle from 'dom-helpers/getComputedStyle';
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 = {};
let visualViewport = typeof window !== 'undefined' && window.visualViewport;
function getContainerDimensions(containerNode) {
var _a, _b;
let width = 0, height = 0, top = 0, left = 0;
let scroll = {};
if (containerNode.tagName === 'BODY') {
width = (_a = visualViewport === null || visualViewport === void 0 ? void 0 : visualViewport.width) !== null && _a !== void 0 ? _a : document.documentElement.clientWidth;
height = (_b = visualViewport === null || visualViewport === void 0 ? void 0 : visualViewport.height) !== null && _b !== void 0 ? _b : document.documentElement.clientHeight;
scroll.top =
getScrollTop(ownerDocument(containerNode).documentElement) ||
getScrollTop(containerNode);
scroll.left =
getScrollLeft(ownerDocument(containerNode).documentElement) ||
getScrollLeft(containerNode);
}
else {
({ width, height, top, left } = getOffset(containerNode));
scroll.top = getScrollTop(containerNode);
scroll.left = getScrollLeft(containerNode);
}
return { width, height, scroll, top, left };
}
function getScroll(node) {
return {
top: node.scrollTop,
left: node.scrollLeft,
width: node.scrollWidth,
height: node.scrollHeight,
};
}
function getDelta(axis, offset, size, containerDimensions, padding) {
let containerScroll = containerDimensions.scroll[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 getMargins(node) {
let style = window.getComputedStyle(node);
return {
top: parseInt(style.marginTop, 10) || 0,
bottom: parseInt(style.marginBottom, 10) || 0,
left: parseInt(style.marginLeft, 10) || 0,
right: parseInt(style.marginRight, 10) || 0,
};
}
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];
}
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;
if (placement === axis) {
const containerHeight = isContainerPositioned
? containerOffsetWithBoundary[size]
: 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 getMaxHeight(position, boundaryDimensions, containerOffsetWithBoundary, childOffset, margins, padding) {
return position.top != null
?
Math.max(0, boundaryDimensions.height +
boundaryDimensions.top +
boundaryDimensions.scroll.top -
(containerOffsetWithBoundary.top + position.top) -
(margins.top + margins.bottom + padding))
:
Math.max(0, childOffset.top +
containerOffsetWithBoundary.top -
(boundaryDimensions.top + boundaryDimensions.scroll.top) -
(margins.top + margins.bottom + padding));
}
function getAvailableSpace(boundaryDimensions, containerOffsetWithBoundary, childOffset, margins, padding, placementInfo) {
let { placement, axis, size } = placementInfo;
if (placement === axis) {
return Math.max(0, childOffset[axis] -
boundaryDimensions[axis] -
boundaryDimensions.scroll[axis] +
containerOffsetWithBoundary[axis] -
margins[axis] -
margins[FLIPPED_DIRECTION[axis]] -
padding);
}
return Math.max(0, boundaryDimensions[size] +
boundaryDimensions[axis] +
boundaryDimensions.scroll[axis] -
containerOffsetWithBoundary[axis] -
childOffset[axis] -
childOffset[size] -
margins[axis] -
margins[FLIPPED_DIRECTION[axis]] -
padding);
}
export 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, axis } = placementInfo;
let position = computePosition(childOffset, boundaryDimensions, overlaySize, placementInfo, offset, crossOffset, containerOffsetWithBoundary, isContainerPositioned);
let isFlipped = false;
let normalizedOffset = offset;
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: maxHeight,
arrowOffsetLeft: arrowPosition.left,
arrowOffsetTop: arrowPosition.top,
placement: placementInfo.placement,
isFlipped,
};
}
export function calculatePosition(opts) {
let { placement, targetNode, overlayNode, scrollNode, padding, shouldFlip, boundaryElement, offset, crossOffset, shouldOverlapWithTrigger, } = opts;
let container = overlayNode.offsetParent || document.body;
let isBodyContainer = container.tagName === 'BODY';
const containerPositionStyle = window.getComputedStyle(container).position;
let isContainerPositioned = !!containerPositionStyle && containerPositionStyle !== 'static';
let childOffset = isBodyContainer
? getOffset(targetNode)
: getPosition(targetNode, container);
if (!isBodyContainer) {
childOffset.top += parseInt(getCss(targetNode, 'marginTop'), 10) || 0;
childOffset.left += parseInt(getCss(targetNode, 'marginLeft'), 10) || 0;
}
let overlaySize = getOffset(overlayNode);
const matrix = getComputedStyle(overlayNode).getPropertyValue('transform');
const transform = matrix;
const regex = /matrix\((-?\d*\.?\d+),\s*(-?\d*\.?\d+),\s*(-?\d*\.?\d+),\s*(-?\d*\.?\d+),\s*(-?\d*\.?\d+),\s*(-?\d*\.?\d+)\)/;
const matches = transform.match(regex);
let scaleX = 1;
let scaleY = 1;
if (matches) {
scaleX = parseFloat(matches[1]);
scaleY = parseFloat(matches[4]);
if (!scaleX || !Number.isFinite(scaleX)) {
scaleX = 1;
}
if (!scaleY || !Number.isFinite(scaleY)) {
scaleY = 1;
}
}
let margins = getMargins(overlayNode);
overlaySize.width += margins.left + margins.right;
overlaySize.height += margins.top + margins.bottom;
if (scaleX) {
overlaySize.width = overlaySize.width / scaleX;
}
if (scaleY) {
overlaySize.height = overlaySize.height / scaleY;
}
let scrollSize = getScroll(scrollNode);
let boundaryDimensions = getContainerDimensions(boundaryElement);
let containerOffsetWithBoundary = boundaryElement.tagName === 'BODY'
? getOffset(container)
: getPosition(container, boundaryElement);
return calculatePositionInternal(placement, childOffset, overlaySize, scrollSize, margins, padding, shouldFlip, boundaryDimensions, containerOffsetWithBoundary, offset, crossOffset, isContainerPositioned, shouldOverlapWithTrigger);
}
//# sourceMappingURL=calculatePosition.js.map