UNPKG

@wordpress/components

Version:
285 lines (242 loc) 10.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.computePopoverXAxisPosition = computePopoverXAxisPosition; exports.computePopoverYAxisPosition = computePopoverYAxisPosition; exports.computePopoverPosition = computePopoverPosition; exports.offsetIframe = offsetIframe; var _i18n = require("@wordpress/i18n"); /** * WordPress dependencies */ /** * Module constants */ const HEIGHT_OFFSET = 10; // used by the arrow and a bit of empty space /** * Utility used to compute the popover position over the xAxis * * @param {Object} anchorRect Anchor Rect. * @param {Object} contentSize Content Size. * @param {string} xAxis Desired xAxis. * @param {string} corner Desired corner. * @param {boolean} stickyBoundaryElement The boundary element to use when * switching between sticky and normal * position. * @param {string} chosenYAxis yAxis to be used. * @param {Element} boundaryElement Boundary element. * @param {boolean} forcePosition Don't adjust position based on anchor. * * @return {Object} Popover xAxis position and constraints. */ function computePopoverXAxisPosition(anchorRect, contentSize, xAxis, corner, stickyBoundaryElement, chosenYAxis, boundaryElement, forcePosition) { const { width } = contentSize; // Correct xAxis for RTL support if (xAxis === 'left' && (0, _i18n.isRTL)()) { xAxis = 'right'; } else if (xAxis === 'right' && (0, _i18n.isRTL)()) { xAxis = 'left'; } if (corner === 'left' && (0, _i18n.isRTL)()) { corner = 'right'; } else if (corner === 'right' && (0, _i18n.isRTL)()) { corner = 'left'; } // x axis alignment choices const anchorMidPoint = Math.round(anchorRect.left + anchorRect.width / 2); const centerAlignment = { popoverLeft: anchorMidPoint, contentWidth: (anchorMidPoint - width / 2 > 0 ? width / 2 : anchorMidPoint) + (anchorMidPoint + width / 2 > window.innerWidth ? window.innerWidth - anchorMidPoint : width / 2) }; let leftAlignmentX = anchorRect.left; if (corner === 'right') { leftAlignmentX = anchorRect.right; } else if (chosenYAxis !== 'middle') { leftAlignmentX = anchorMidPoint; } let rightAlignmentX = anchorRect.right; if (corner === 'left') { rightAlignmentX = anchorRect.left; } else if (chosenYAxis !== 'middle') { rightAlignmentX = anchorMidPoint; } const leftAlignment = { popoverLeft: leftAlignmentX, contentWidth: leftAlignmentX - width > 0 ? width : leftAlignmentX }; const rightAlignment = { popoverLeft: rightAlignmentX, contentWidth: rightAlignmentX + width > window.innerWidth ? window.innerWidth - rightAlignmentX : width }; // Choosing the x axis let chosenXAxis = xAxis; let contentWidth = null; if (!stickyBoundaryElement && !forcePosition) { if (xAxis === 'center' && centerAlignment.contentWidth === width) { chosenXAxis = 'center'; } else if (xAxis === 'left' && leftAlignment.contentWidth === width) { chosenXAxis = 'left'; } else if (xAxis === 'right' && rightAlignment.contentWidth === width) { chosenXAxis = 'right'; } else { chosenXAxis = leftAlignment.contentWidth > rightAlignment.contentWidth ? 'left' : 'right'; const chosenWidth = chosenXAxis === 'left' ? leftAlignment.contentWidth : rightAlignment.contentWidth; // Limit width of the content to the viewport width if (width > window.innerWidth) { contentWidth = window.innerWidth; } // If we can't find any alignment options that could fit // our content, then let's fallback to the center of the viewport. if (chosenWidth !== width) { chosenXAxis = 'center'; centerAlignment.popoverLeft = window.innerWidth / 2; } } } let popoverLeft; if (chosenXAxis === 'center') { popoverLeft = centerAlignment.popoverLeft; } else if (chosenXAxis === 'left') { popoverLeft = leftAlignment.popoverLeft; } else { popoverLeft = rightAlignment.popoverLeft; } if (boundaryElement) { const boundaryRect = boundaryElement.getBoundingClientRect(); popoverLeft = Math.min(popoverLeft, boundaryRect.right - width); // Avoid the popover being position beyond the left boundary if the // direction is left to right. if (!(0, _i18n.isRTL)()) { popoverLeft = Math.max(popoverLeft, 0); } } return { xAxis: chosenXAxis, popoverLeft, contentWidth }; } /** * Utility used to compute the popover position over the yAxis * * @param {Object} anchorRect Anchor Rect. * @param {Object} contentSize Content Size. * @param {string} yAxis Desired yAxis. * @param {string} corner Desired corner. * @param {boolean} stickyBoundaryElement The boundary element to use when * switching between sticky and normal * position. * @param {Element} anchorRef The anchor element. * @param {Element} relativeOffsetTop If applicable, top offset of the * relative positioned parent container. * @param {boolean} forcePosition Don't adjust position based on anchor. * * @return {Object} Popover xAxis position and constraints. */ function computePopoverYAxisPosition(anchorRect, contentSize, yAxis, corner, stickyBoundaryElement, anchorRef, relativeOffsetTop, forcePosition) { const { height } = contentSize; if (stickyBoundaryElement) { const stickyRect = stickyBoundaryElement.getBoundingClientRect(); const stickyPosition = stickyRect.top + height - relativeOffsetTop; if (anchorRect.top <= stickyPosition) { return { yAxis, popoverTop: Math.min(anchorRect.bottom, stickyPosition) }; } } // y axis alignment choices let anchorMidPoint = anchorRect.top + anchorRect.height / 2; if (corner === 'bottom') { anchorMidPoint = anchorRect.bottom; } else if (corner === 'top') { anchorMidPoint = anchorRect.top; } const middleAlignment = { popoverTop: anchorMidPoint, contentHeight: (anchorMidPoint - height / 2 > 0 ? height / 2 : anchorMidPoint) + (anchorMidPoint + height / 2 > window.innerHeight ? window.innerHeight - anchorMidPoint : height / 2) }; const topAlignment = { popoverTop: anchorRect.top, contentHeight: anchorRect.top - HEIGHT_OFFSET - height > 0 ? height : anchorRect.top - HEIGHT_OFFSET }; const bottomAlignment = { popoverTop: anchorRect.bottom, contentHeight: anchorRect.bottom + HEIGHT_OFFSET + height > window.innerHeight ? window.innerHeight - HEIGHT_OFFSET - anchorRect.bottom : height }; // Choosing the y axis let chosenYAxis = yAxis; let contentHeight = null; if (!stickyBoundaryElement && !forcePosition) { if (yAxis === 'middle' && middleAlignment.contentHeight === height) { chosenYAxis = 'middle'; } else if (yAxis === 'top' && topAlignment.contentHeight === height) { chosenYAxis = 'top'; } else if (yAxis === 'bottom' && bottomAlignment.contentHeight === height) { chosenYAxis = 'bottom'; } else { chosenYAxis = topAlignment.contentHeight > bottomAlignment.contentHeight ? 'top' : 'bottom'; const chosenHeight = chosenYAxis === 'top' ? topAlignment.contentHeight : bottomAlignment.contentHeight; contentHeight = chosenHeight !== height ? chosenHeight : null; } } let popoverTop; if (chosenYAxis === 'middle') { popoverTop = middleAlignment.popoverTop; } else if (chosenYAxis === 'top') { popoverTop = topAlignment.popoverTop; } else { popoverTop = bottomAlignment.popoverTop; } return { yAxis: chosenYAxis, popoverTop, contentHeight }; } /** * Utility used to compute the popover position and the content max width/height * for a popover given its anchor rect and its content size. * * @param {Object} anchorRect Anchor Rect. * @param {Object} contentSize Content Size. * @param {string} position Position. * @param {boolean} stickyBoundaryElement The boundary element to use when * switching between sticky and normal * position. * @param {Element} anchorRef The anchor element. * @param {number} relativeOffsetTop If applicable, top offset of the * relative positioned parent container. * @param {Element} boundaryElement Boundary element. * @param {boolean} forcePosition Don't adjust position based on anchor. * * @return {Object} Popover position and constraints. */ function computePopoverPosition(anchorRect, contentSize, position = 'top', stickyBoundaryElement, anchorRef, relativeOffsetTop, boundaryElement, forcePosition) { const [yAxis, xAxis = 'center', corner] = position.split(' '); const yAxisPosition = computePopoverYAxisPosition(anchorRect, contentSize, yAxis, corner, stickyBoundaryElement, anchorRef, relativeOffsetTop, forcePosition); const xAxisPosition = computePopoverXAxisPosition(anchorRect, contentSize, xAxis, corner, stickyBoundaryElement, yAxisPosition.yAxis, boundaryElement, forcePosition); return { ...xAxisPosition, ...yAxisPosition }; } /** * Offsets the given rect by the position of the iframe that contains the element. * If the owner document is not in an iframe then it returns with the original rect. * * @param {DOMRect} rect bounds of the element * @param {Document} ownerDocument document of the element * * @return {DOMRect} offsetted bounds */ function offsetIframe(rect, ownerDocument) { const { defaultView } = ownerDocument; const { frameElement } = defaultView; if (!frameElement) { return rect; } const iframeRect = frameElement.getBoundingClientRect(); return new defaultView.DOMRect(rect.left + iframeRect.left, rect.top + iframeRect.top, rect.width, rect.height); } //# sourceMappingURL=utils.js.map