UNPKG

@gluestack-ui/core

Version:

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

256 lines 11.1 kB
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