UNPKG

@fluentui/react

Version:

Reusable React components for building web experiences.

808 lines 42.7 kB
"use strict"; var _a; Object.defineProperty(exports, "__esModule", { value: true }); exports.__positioningTestPackage = void 0; exports.positionElement = positionElement; exports.positionCallout = positionCallout; exports.positionCard = positionCard; exports.getMaxHeight = getMaxHeight; exports.getOppositeEdge = getOppositeEdge; exports.getBoundsFromTargetWindow = getBoundsFromTargetWindow; exports.calculateGapSpace = calculateGapSpace; exports.getRectangleFromTarget = getRectangleFromTarget; var tslib_1 = require("tslib"); var DirectionalHint_1 = require("../../common/DirectionalHint"); var Utilities_1 = require("../../Utilities"); var positioning_types_1 = require("./positioning.types"); var Utilities_2 = require("../../Utilities"); function _createPositionData(targetEdge, alignmentEdge, isAuto) { return { targetEdge: targetEdge, alignmentEdge: alignmentEdge, isAuto: isAuto, }; } // Currently the beakPercent is set to 50 for all positions meaning that it should tend to the center of the target var DirectionalDictionary = (_a = {}, _a[DirectionalHint_1.DirectionalHint.topLeftEdge] = _createPositionData(positioning_types_1.RectangleEdge.top, positioning_types_1.RectangleEdge.left), _a[DirectionalHint_1.DirectionalHint.topCenter] = _createPositionData(positioning_types_1.RectangleEdge.top), _a[DirectionalHint_1.DirectionalHint.topRightEdge] = _createPositionData(positioning_types_1.RectangleEdge.top, positioning_types_1.RectangleEdge.right), _a[DirectionalHint_1.DirectionalHint.topAutoEdge] = _createPositionData(positioning_types_1.RectangleEdge.top, undefined, true), _a[DirectionalHint_1.DirectionalHint.bottomLeftEdge] = _createPositionData(positioning_types_1.RectangleEdge.bottom, positioning_types_1.RectangleEdge.left), _a[DirectionalHint_1.DirectionalHint.bottomCenter] = _createPositionData(positioning_types_1.RectangleEdge.bottom), _a[DirectionalHint_1.DirectionalHint.bottomRightEdge] = _createPositionData(positioning_types_1.RectangleEdge.bottom, positioning_types_1.RectangleEdge.right), _a[DirectionalHint_1.DirectionalHint.bottomAutoEdge] = _createPositionData(positioning_types_1.RectangleEdge.bottom, undefined, true), _a[DirectionalHint_1.DirectionalHint.leftTopEdge] = _createPositionData(positioning_types_1.RectangleEdge.left, positioning_types_1.RectangleEdge.top), _a[DirectionalHint_1.DirectionalHint.leftCenter] = _createPositionData(positioning_types_1.RectangleEdge.left), _a[DirectionalHint_1.DirectionalHint.leftBottomEdge] = _createPositionData(positioning_types_1.RectangleEdge.left, positioning_types_1.RectangleEdge.bottom), _a[DirectionalHint_1.DirectionalHint.rightTopEdge] = _createPositionData(positioning_types_1.RectangleEdge.right, positioning_types_1.RectangleEdge.top), _a[DirectionalHint_1.DirectionalHint.rightCenter] = _createPositionData(positioning_types_1.RectangleEdge.right), _a[DirectionalHint_1.DirectionalHint.rightBottomEdge] = _createPositionData(positioning_types_1.RectangleEdge.right, positioning_types_1.RectangleEdge.bottom), _a); function _isRectangleWithinBounds(rect, boundingRect) { if (rect.top < boundingRect.top) { return false; } if (rect.bottom > boundingRect.bottom) { return false; } if (rect.left < boundingRect.left) { return false; } if (rect.right > boundingRect.right) { return false; } return true; } /** * Gets all of the edges of a rectangle that are outside of the given bounds. * If there are no out of bounds edges it returns an empty array. */ function _getOutOfBoundsEdges(rect, boundingRect) { var outOfBounds = []; if (rect.top < boundingRect.top) { outOfBounds.push(positioning_types_1.RectangleEdge.top); } if (rect.bottom > boundingRect.bottom) { outOfBounds.push(positioning_types_1.RectangleEdge.bottom); } if (rect.left < boundingRect.left) { outOfBounds.push(positioning_types_1.RectangleEdge.left); } if (rect.right > boundingRect.right) { outOfBounds.push(positioning_types_1.RectangleEdge.right); } return outOfBounds; } function _getEdgeValue(rect, edge) { return rect[positioning_types_1.RectangleEdge[edge]]; } function _setEdgeValue(rect, edge, value) { rect[positioning_types_1.RectangleEdge[edge]] = value; return rect; } /** * Returns the middle value of an edge. Only returns 1 value rather than xy coordinates as * the itself already contains the other coordinate. * For instance, a bottom edge's current value is it's y coordinate, so the number returned is the x. */ function _getCenterValue(rect, edge) { var edges = _getFlankingEdges(edge); return (_getEdgeValue(rect, edges.positiveEdge) + _getEdgeValue(rect, edges.negativeEdge)) / 2; } /** * Flips the value depending on the edge. * If the edge is a "positive" edge, Top or Left, then the value should stay as it is. * If the edge is a "negative" edge, Bottom or Right, then the value should be flipped. * This is to account for the fact that the coordinates are effectively reserved in certain cases for the * "negative" edges. * * For example, when testing to see if a bottom edge 1 is within the bounds of another bottom edge 2: * If edge 1 is greater than edge 2 then it is out of bounds. This is reversed for top edge 1 and top edge 2. * If top edge 1 is less than edge 2 then it is out of bounds. */ function _getRelativeEdgeValue(edge, value) { if (edge > 0) { return value; } else { return value * -1; } } function _getRelativeRectEdgeValue(edge, rect) { return _getRelativeEdgeValue(edge, _getEdgeValue(rect, edge)); } function _getRelativeEdgeDifference(rect, hostRect, edge) { var edgeDifference = _getEdgeValue(rect, edge) - _getEdgeValue(hostRect, edge); return _getRelativeEdgeValue(edge, edgeDifference); } /** * Moves the edge of a rectangle to the value given. It only moves the edge in a linear direction based on that edge. * For example, if it's a bottom edge it will only change y coordinates. * if maintainSize is set to false, it will only adjust the specified edge value */ function _moveEdge(rect, edge, newValue, maintainSize) { if (maintainSize === void 0) { maintainSize = true; } var difference = _getEdgeValue(rect, edge) - newValue; var returnRect = _setEdgeValue(rect, edge, newValue); if (maintainSize) { returnRect = _setEdgeValue(rect, edge * -1, _getEdgeValue(rect, edge * -1) - difference); } return returnRect; } /** * Aligns the edge on the passed in rect to the target. If there is a gap then it will have that space between the two. */ function _alignEdges(rect, target, edge, gap) { if (gap === void 0) { gap = 0; } return _moveEdge(rect, edge, _getEdgeValue(target, edge) + _getRelativeEdgeValue(edge, gap)); } /** * Aligns the targetEdge on the passed in target to the rects corresponding opposite edge. * For instance if targetEdge is bottom, then the rects top will be moved to match it. */ function _alignOppositeEdges(rect, target, targetEdge, gap) { if (gap === void 0) { gap = 0; } var oppositeEdge = targetEdge * -1; var adjustedGap = _getRelativeEdgeValue(oppositeEdge, gap); return _moveEdge(rect, targetEdge * -1, _getEdgeValue(target, targetEdge) + adjustedGap); } /** * Tests to see if the given edge is within the bounds of the given rectangle. */ function _isEdgeInBounds(rect, bounds, edge) { var adjustedRectValue = _getRelativeRectEdgeValue(edge, rect); return adjustedRectValue > _getRelativeRectEdgeValue(edge, bounds); } /** * Returns a measure of how much a rectangle is out of bounds for a given alignment; * this can be used to compare which rectangle is more or less out of bounds. * A value of 0 means the rectangle is entirely in bounds */ function _getOutOfBoundsDegree(rect, bounds) { var breakingEdges = _getOutOfBoundsEdges(rect, bounds); var total = 0; for (var _i = 0, breakingEdges_1 = breakingEdges; _i < breakingEdges_1.length; _i++) { var edge = breakingEdges_1[_i]; total += Math.pow(_getRelativeEdgeDifference(rect, bounds, edge), 2); } return total; } /** * Returns true if scroll-resizing will move the target edge within the bounding rectangle, * and there is room between the target edge and the bounding edge for scrolled content. * Returns false otherwise. */ function _canScrollResizeToFitEdge(target, bounding, targetEdge, minimumScrollResizeHeight) { if (minimumScrollResizeHeight === void 0) { minimumScrollResizeHeight = 200; } // Only scroll vertically to fit - cannot scroll to fit right or left edges if (targetEdge !== positioning_types_1.RectangleEdge.bottom && targetEdge !== positioning_types_1.RectangleEdge.top) { return false; } return _getRelativeEdgeDifference(target, bounding, targetEdge) >= minimumScrollResizeHeight; } /** * Attempts to move the rectangle through various sides of the target to find a place to fit. * If no fit is found, the least bad option should be returned. */ function _flipToFit(rect, target, bounding, positionData, shouldScroll, minimumScrollResizeHeight, gap) { if (shouldScroll === void 0) { shouldScroll = false; } if (gap === void 0) { gap = 0; } var directions = [ positioning_types_1.RectangleEdge.left, positioning_types_1.RectangleEdge.right, positioning_types_1.RectangleEdge.bottom, positioning_types_1.RectangleEdge.top, ]; // In RTL page, RectangleEdge.right has a higher priority than RectangleEdge.left, so the order should be updated. if ((0, Utilities_1.getRTL)()) { directions[0] *= -1; directions[1] *= -1; } var currentEstimate = rect; var currentEdge = positionData.targetEdge; var currentAlignment = positionData.alignmentEdge; // keep track of least bad option, in case no sides fit var oobDegree; var bestEdge = currentEdge; var bestAlignment = currentAlignment; // Keep switching sides until one is found with enough space. // If all sides don't fit then return the unmodified element. for (var i = 0; i < 4; i++) { if (_isEdgeInBounds(currentEstimate, bounding, currentEdge)) { // Edge is in bounds, return current estimate return { elementRectangle: currentEstimate, targetEdge: currentEdge, alignmentEdge: currentAlignment, }; } else if (shouldScroll && _canScrollResizeToFitEdge(target, bounding, currentEdge, minimumScrollResizeHeight)) { // Scrolling will allow edge to fit, move the estimate currentEdge inside the bounds and return switch (currentEdge) { case positioning_types_1.RectangleEdge.bottom: currentEstimate.bottom = bounding.bottom; break; case positioning_types_1.RectangleEdge.top: currentEstimate.top = bounding.top; break; } return { elementRectangle: currentEstimate, targetEdge: currentEdge, alignmentEdge: currentAlignment, forcedInBounds: true, }; } else { // update least-bad edges var currentOOBDegree = _getOutOfBoundsDegree(currentEstimate, bounding); if (!oobDegree || currentOOBDegree < oobDegree) { oobDegree = currentOOBDegree; bestEdge = currentEdge; bestAlignment = currentAlignment; } directions.splice(directions.indexOf(currentEdge), 1); if (directions.length > 0) { if (directions.indexOf(currentEdge * -1) > -1) { currentEdge = currentEdge * -1; } else { currentAlignment = currentEdge; currentEdge = directions.slice(-1)[0]; } currentEstimate = _estimatePosition(rect, target, { targetEdge: currentEdge, alignmentEdge: currentAlignment }, gap); } } } // nothing fits, use least-bad option currentEstimate = _estimatePosition(rect, target, { targetEdge: bestEdge, alignmentEdge: bestAlignment }, gap); return { elementRectangle: currentEstimate, targetEdge: bestEdge, alignmentEdge: bestAlignment, }; } /** * Flips only the alignment edge of an element rectangle. This is used instead of nudging the alignment edges * into position, when `alignTargetEdge` is specified. */ function _flipAlignmentEdge(elementEstimate, target, gap, coverTarget) { var alignmentEdge = elementEstimate.alignmentEdge, targetEdge = elementEstimate.targetEdge, elementRectangle = elementEstimate.elementRectangle; var oppositeEdge = alignmentEdge * -1; var newEstimate = _estimatePosition(elementRectangle, target, { targetEdge: targetEdge, alignmentEdge: oppositeEdge }, gap, coverTarget); return { elementRectangle: newEstimate, targetEdge: targetEdge, alignmentEdge: oppositeEdge, }; } /** * Adjusts a element rectangle to fit within the bounds given. If directionalHintFixed or covertarget is passed in * then the element will not flip sides on the target. They will, however, be nudged to fit within the bounds given. */ function _adjustFitWithinBounds(element, target, bounding, positionData, shouldScroll, minimumScrollResizeHeight, gap, directionalHintFixed, coverTarget) { if (shouldScroll === void 0) { shouldScroll = false; } if (gap === void 0) { gap = 0; } var alignmentEdge = positionData.alignmentEdge, alignTargetEdge = positionData.alignTargetEdge; var elementEstimate = { elementRectangle: element, targetEdge: positionData.targetEdge, alignmentEdge: alignmentEdge, }; if (!directionalHintFixed && !coverTarget) { elementEstimate = _flipToFit(element, target, bounding, positionData, shouldScroll, minimumScrollResizeHeight, gap); } var outOfBounds = _getOutOfBoundsEdges(elementEstimate.elementRectangle, bounding); // if directionalHintFixed is specified, we need to force the target edge to not change // we need *-1 because targetEdge refers to the target's edge; the callout edge is the opposite var fixedEdge = directionalHintFixed ? -elementEstimate.targetEdge : undefined; if (outOfBounds.length > 0) { if (alignTargetEdge) { // The edge opposite to the alignment edge might be out of bounds. // Flip alignment to see if we can get it within bounds. if (elementEstimate.alignmentEdge && outOfBounds.indexOf(elementEstimate.alignmentEdge * -1) > -1) { var flippedElementEstimate = _flipAlignmentEdge(elementEstimate, target, gap, coverTarget); if (_isRectangleWithinBounds(flippedElementEstimate.elementRectangle, bounding)) { return flippedElementEstimate; } else { // If the flipped elements edges are still out of bounds, try nudging it. elementEstimate = _alignOutOfBoundsEdges(_getOutOfBoundsEdges(flippedElementEstimate.elementRectangle, bounding), elementEstimate, bounding, fixedEdge); } } else { elementEstimate = _alignOutOfBoundsEdges(outOfBounds, elementEstimate, bounding, fixedEdge); } } else { elementEstimate = _alignOutOfBoundsEdges(outOfBounds, elementEstimate, bounding, fixedEdge); } } return elementEstimate; } /** * Iterates through a list of out of bounds edges and tries to nudge and align them. * @param outOfBoundsEdges - Array of edges that are out of bounds * @param elementEstimate - The current element positioning estimate * @param bounding - The current bounds * @param preserveEdge - Specify an edge that should not be modified */ function _alignOutOfBoundsEdges(outOfBoundsEdges, elementEstimate, bounding, preserveEdge) { for (var _i = 0, outOfBoundsEdges_1 = outOfBoundsEdges; _i < outOfBoundsEdges_1.length; _i++) { var direction = outOfBoundsEdges_1[_i]; var edgeAttempt = void 0; // if preserveEdge is specified, do not call _alignEdges, skip directly to _moveEdge // this is because _alignEdges will move the opposite edge if (preserveEdge && preserveEdge === direction * -1) { edgeAttempt = _moveEdge(elementEstimate.elementRectangle, direction, _getEdgeValue(bounding, direction), false); elementEstimate.forcedInBounds = true; } else { edgeAttempt = _alignEdges(elementEstimate.elementRectangle, bounding, direction); var inBounds = _isEdgeInBounds(edgeAttempt, bounding, direction * -1); // only update estimate if the attempt didn't break out of the opposite bounding edge if (!inBounds) { edgeAttempt = _moveEdge(edgeAttempt, direction * -1, _getEdgeValue(bounding, direction * -1), false); elementEstimate.forcedInBounds = true; } } elementEstimate.elementRectangle = edgeAttempt; } return elementEstimate; } /** * Moves the middle point on an edge to the point given. * Only moves in one direction. For instance if a bottom edge is passed in, then * the bottom edge will be moved in the x axis to match the point. */ function _centerEdgeToPoint(rect, edge, point) { var positiveEdge = _getFlankingEdges(edge).positiveEdge; var elementMiddle = _getCenterValue(rect, edge); var distanceToMiddle = elementMiddle - _getEdgeValue(rect, positiveEdge); return _moveEdge(rect, positiveEdge, point - distanceToMiddle); } /** * Moves the element rectangle to be appropriately positioned relative to a given target. * Does not flip or adjust the element. */ function _estimatePosition(elementToPosition, target, positionData, gap, coverTarget) { if (gap === void 0) { gap = 0; } var estimatedElementPosition = new Utilities_2.Rectangle(elementToPosition.left, elementToPosition.right, elementToPosition.top, elementToPosition.bottom); var alignmentEdge = positionData.alignmentEdge, targetEdge = positionData.targetEdge; var elementEdge = coverTarget ? targetEdge : targetEdge * -1; estimatedElementPosition = coverTarget ? _alignEdges(estimatedElementPosition, target, targetEdge, gap) : _alignOppositeEdges(estimatedElementPosition, target, targetEdge, gap); // if no alignment edge is provided it's supposed to be centered. if (!alignmentEdge) { var targetMiddlePoint = _getCenterValue(target, targetEdge); estimatedElementPosition = _centerEdgeToPoint(estimatedElementPosition, elementEdge, targetMiddlePoint); } else { estimatedElementPosition = _alignEdges(estimatedElementPosition, target, alignmentEdge); } return estimatedElementPosition; } /** * Returns the non-opposite edges of the target edge. * For instance if bottom is passed in then left and right will be returned. */ function _getFlankingEdges(edge) { if (edge === positioning_types_1.RectangleEdge.top || edge === positioning_types_1.RectangleEdge.bottom) { return { positiveEdge: positioning_types_1.RectangleEdge.left, negativeEdge: positioning_types_1.RectangleEdge.right, }; } else { return { positiveEdge: positioning_types_1.RectangleEdge.top, negativeEdge: positioning_types_1.RectangleEdge.bottom, }; } } /** * Retrieve the final value for the return edge of `elementRectangle`. If the `elementRectangle` is closer to one side * of the bounds versus the other, the return edge is flipped to grow inward. */ function _finalizeReturnEdge(elementRectangle, returnEdge, bounds) { if (bounds && Math.abs(_getRelativeEdgeDifference(elementRectangle, bounds, returnEdge)) > Math.abs(_getRelativeEdgeDifference(elementRectangle, bounds, returnEdge * -1))) { return returnEdge * -1; } return returnEdge; } /** * Whether or not the considered edge of the elementRectangle is lying on the edge of the bounds * @param elementRectangle The rectangle whose edge we are considering * @param bounds The rectangle marking the bounds * @param edge The target edge we're considering * @returns If the target edge of the elementRectangle is in the same location as that edge of the bounds */ function _isEdgeOnBounds(elementRectangle, edge, bounds) { return bounds !== undefined && _getEdgeValue(elementRectangle, edge) === _getEdgeValue(bounds, edge); } /** * Finalizes the element position based on the hostElement. Only returns the * rectangle values to position such that they are anchored to the target. * This helps prevent resizing from looking very strange. * For instance, if the target edge is top and aligned with the left side then * the bottom and left values are returned so as the Callout shrinks it shrinks towards that corner. */ function _finalizeElementPosition(elementRectangle, hostElement, targetEdge, bounds, alignmentEdge, coverTarget, doNotFinalizeReturnEdge, forceWithinBounds) { var returnValue = {}; var hostRect = _getRectangleFromElement(hostElement); var elementEdge = coverTarget ? targetEdge : targetEdge * -1; var returnEdge = alignmentEdge ? alignmentEdge : _getFlankingEdges(targetEdge).positiveEdge; // If we are finalizing the return edge, choose the edge such that we grow away from the bounds // If we are not finalizing the return edge but the opposite edge is flush against the bounds, // choose that as the anchor edge so the element rect can grow away from the bounds' edge // In this case there will not be a visual difference because there is no more room for the elementRectangle to grow // in the usual direction if (!doNotFinalizeReturnEdge || _isEdgeOnBounds(elementRectangle, getOppositeEdge(returnEdge), bounds)) { returnEdge = _finalizeReturnEdge(elementRectangle, returnEdge, bounds); } returnValue[positioning_types_1.RectangleEdge[elementEdge]] = _getRelativeEdgeDifference(elementRectangle, hostRect, elementEdge); returnValue[positioning_types_1.RectangleEdge[returnEdge]] = _getRelativeEdgeDifference(elementRectangle, hostRect, returnEdge); // if the positioned element will still overflow, return all four edges with in-bounds values if (forceWithinBounds) { returnValue[positioning_types_1.RectangleEdge[elementEdge * -1]] = _getRelativeEdgeDifference(elementRectangle, hostRect, elementEdge * -1); returnValue[positioning_types_1.RectangleEdge[returnEdge * -1]] = _getRelativeEdgeDifference(elementRectangle, hostRect, returnEdge * -1); } return returnValue; } // Since the beak is rotated 45 degrees the actual height/width is the length of the diagonal. // We still want to position the beak based on it's midpoint which does not change. It will // be at (beakwidth / 2, beakwidth / 2) function _calculateActualBeakWidthInPixels(beakWidth) { return Math.sqrt(beakWidth * beakWidth * 2); } /** * Returns the appropriate IPositionData based on the props altered for RTL. * If directionalHintForRTL is passed in that is used if the page is RTL. * If directionalHint is specified, no directionalHintForRTL is available, and the page is RTL, the hint will be * flipped (e.g. bottomLeftEdge would become bottomRightEdge). * * If there is no directionalHint passed in, bottomAutoEdge is chosen automatically. */ function _getPositionData(directionalHint, directionalHintForRTL, previousPositions) { if (directionalHint === void 0) { directionalHint = DirectionalHint_1.DirectionalHint.bottomAutoEdge; } if (previousPositions) { return { alignmentEdge: previousPositions.alignmentEdge, isAuto: previousPositions.isAuto, targetEdge: previousPositions.targetEdge, }; } var positionInformation = tslib_1.__assign({}, DirectionalDictionary[directionalHint]); if ((0, Utilities_1.getRTL)()) { // If alignment edge exists and that alignment edge is -2 or 2, right or left, then flip it. if (positionInformation.alignmentEdge && positionInformation.alignmentEdge % 2 === 0) { positionInformation.alignmentEdge = positionInformation.alignmentEdge * -1; } return directionalHintForRTL !== undefined ? DirectionalDictionary[directionalHintForRTL] : positionInformation; } return positionInformation; } /** * Gets the alignment data for the given information. This only really matters if the positioning is Auto. * If it is auto then the alignmentEdge should be chosen based on the target edge's position relative to * the center of the page. */ function _getAlignmentData(positionData, target, boundingRect, coverTarget, alignTargetEdge) { if (positionData.isAuto) { positionData.alignmentEdge = getClosestEdge(positionData.targetEdge, target, boundingRect); } positionData.alignTargetEdge = alignTargetEdge; return positionData; } function getClosestEdge(targetEdge, target, boundingRect) { var targetCenter = _getCenterValue(target, targetEdge); var boundingCenter = _getCenterValue(boundingRect, targetEdge); var _a = _getFlankingEdges(targetEdge), positiveEdge = _a.positiveEdge, negativeEdge = _a.negativeEdge; if (targetCenter <= boundingCenter) { return positiveEdge; } else { return negativeEdge; } } function _positionElementWithinBounds(elementToPosition, target, bounding, positionData, gap, shouldScroll, minimumScrollResizeHeight, directionalHintFixed, coverTarget) { if (shouldScroll === void 0) { shouldScroll = false; } var estimatedElementPosition = _estimatePosition(elementToPosition, target, positionData, gap, coverTarget); if (_isRectangleWithinBounds(estimatedElementPosition, bounding)) { return { elementRectangle: estimatedElementPosition, targetEdge: positionData.targetEdge, alignmentEdge: positionData.alignmentEdge, }; } else { return _adjustFitWithinBounds(estimatedElementPosition, target, bounding, positionData, shouldScroll, minimumScrollResizeHeight, gap, directionalHintFixed, coverTarget); } } function _finalizeBeakPosition(elementPosition, positionedBeak, bounds) { var targetEdge = elementPosition.targetEdge * -1; // The "host" element that we will use to help position the beak. var actualElement = new Utilities_2.Rectangle(0, elementPosition.elementRectangle.width, 0, elementPosition.elementRectangle.height); var returnValue = {}; var returnEdge = _finalizeReturnEdge(elementPosition.elementRectangle, elementPosition.alignmentEdge ? elementPosition.alignmentEdge : _getFlankingEdges(targetEdge).positiveEdge, bounds); // only show the beak if the callout is not fully covering the target var beakEdgeDifference = _getRelativeEdgeDifference(elementPosition.elementRectangle, elementPosition.targetRectangle, targetEdge); var showBeak = beakEdgeDifference > Math.abs(_getEdgeValue(positionedBeak, targetEdge)); returnValue[positioning_types_1.RectangleEdge[targetEdge]] = _getEdgeValue(positionedBeak, targetEdge); returnValue[positioning_types_1.RectangleEdge[returnEdge]] = _getRelativeEdgeDifference(positionedBeak, actualElement, returnEdge); return { elementPosition: tslib_1.__assign({}, returnValue), closestEdge: getClosestEdge(elementPosition.targetEdge, positionedBeak, actualElement), targetEdge: targetEdge, hideBeak: !showBeak, }; } function _positionBeak(beakWidth, elementPosition) { var target = elementPosition.targetRectangle; /** * Note about beak positioning: The actual beak width only matters for getting the gap between the callout and * target, it does not impact the beak placement within the callout. For example example, if the beakWidth is 8, * then the actual beakWidth is sqrroot(8^2 + 8^2) = 11.31x11.31. So the callout will need to be an extra 3 pixels * away from its target. While the beak is being positioned in the callout it still acts as though it were 8x8. */ var _a = _getFlankingEdges(elementPosition.targetEdge), positiveEdge = _a.positiveEdge, negativeEdge = _a.negativeEdge; var beakTargetPoint = _getCenterValue(target, elementPosition.targetEdge); var elementBounds = new Utilities_2.Rectangle(beakWidth / 2, elementPosition.elementRectangle.width - beakWidth / 2, beakWidth / 2, elementPosition.elementRectangle.height - beakWidth / 2); var beakPosition = new Utilities_2.Rectangle(0, beakWidth, 0, beakWidth); beakPosition = _moveEdge(beakPosition, elementPosition.targetEdge * -1, -beakWidth / 2); beakPosition = _centerEdgeToPoint(beakPosition, elementPosition.targetEdge * -1, beakTargetPoint - _getRelativeRectEdgeValue(positiveEdge, elementPosition.elementRectangle)); if (!_isEdgeInBounds(beakPosition, elementBounds, positiveEdge)) { beakPosition = _alignEdges(beakPosition, elementBounds, positiveEdge); } else if (!_isEdgeInBounds(beakPosition, elementBounds, negativeEdge)) { beakPosition = _alignEdges(beakPosition, elementBounds, negativeEdge); } return beakPosition; } function _getRectangleFromElement(element) { // eslint-disable-next-line @typescript-eslint/no-deprecated var clientRect = element.getBoundingClientRect(); return new Utilities_2.Rectangle(clientRect.left, clientRect.right, clientRect.top, clientRect.bottom); } function _getRectangleFromIRect(rect) { return new Utilities_2.Rectangle(rect.left, rect.right, rect.top, rect.bottom); } function _getTargetRect(bounds, target) { var targetRectangle; if (target) { // eslint-disable-next-line no-extra-boolean-cast if (!!target.preventDefault) { var ev = target; targetRectangle = new Utilities_2.Rectangle(ev.clientX, ev.clientX, ev.clientY, ev.clientY); // eslint-disable-next-line no-extra-boolean-cast } else if (!!target.getBoundingClientRect) { targetRectangle = _getRectangleFromElement(target); // HTMLImgElements can have x and y values. The check for it being a point must go last. } else { var rectOrPoint = target; // eslint-disable-next-line @typescript-eslint/no-deprecated var left = rectOrPoint.left || rectOrPoint.x; // eslint-disable-next-line @typescript-eslint/no-deprecated var top_1 = rectOrPoint.top || rectOrPoint.y; var right = rectOrPoint.right || left; var bottom = rectOrPoint.bottom || top_1; targetRectangle = new Utilities_2.Rectangle(left, right, top_1, bottom); } if (!_isRectangleWithinBounds(targetRectangle, bounds)) { var outOfBounds = _getOutOfBoundsEdges(targetRectangle, bounds); for (var _i = 0, outOfBounds_1 = outOfBounds; _i < outOfBounds_1.length; _i++) { var direction = outOfBounds_1[_i]; targetRectangle[positioning_types_1.RectangleEdge[direction]] = bounds[positioning_types_1.RectangleEdge[direction]]; } } } else { targetRectangle = new Utilities_2.Rectangle(0, 0, 0, 0); } return targetRectangle; } /** * If max height is less than zero it returns the bounds height instead. */ function _getMaxHeightFromTargetRectangle(targetRectangle, targetEdge, gapSpace, bounds, coverTarget) { var maxHeight = 0; var directionalHint = DirectionalDictionary[targetEdge]; // If cover target is set, then the max height should be calculated using the opposite of the target edge since // that's the direction that the callout will expand in. // For instance, if the directionalhint is bottomLeftEdge then the callout will position so it's bottom edge // is aligned with the bottom of the target and expand up towards the top of the screen and the calculated max height // is (bottom of target) - (top of screen) - gapSpace. var target = coverTarget ? directionalHint.targetEdge * -1 : directionalHint.targetEdge; if (target === positioning_types_1.RectangleEdge.top) { maxHeight = _getEdgeValue(targetRectangle, directionalHint.targetEdge) - bounds.top - gapSpace; } else if (target === positioning_types_1.RectangleEdge.bottom) { maxHeight = bounds.bottom - _getEdgeValue(targetRectangle, directionalHint.targetEdge) - gapSpace; } else { maxHeight = bounds.bottom - targetRectangle.top - gapSpace; } return maxHeight > 0 ? maxHeight : bounds.height; } function _positionElementRelative(props, elementToPosition, boundingRect, previousPositions, shouldScroll, minimumScrollResizeHeight) { if (shouldScroll === void 0) { shouldScroll = false; } var gap = props.gapSpace ? props.gapSpace : 0; var targetRect = _getTargetRect(boundingRect, props.target); var positionData = _getAlignmentData(_getPositionData(props.directionalHint, props.directionalHintForRTL, previousPositions), targetRect, boundingRect, props.coverTarget, props.alignTargetEdge); var positionedElement = _positionElementWithinBounds(_getRectangleFromElement(elementToPosition), targetRect, boundingRect, positionData, gap, shouldScroll, minimumScrollResizeHeight, props.directionalHintFixed, props.coverTarget); return tslib_1.__assign(tslib_1.__assign({}, positionedElement), { targetRectangle: targetRect }); } function _finalizePositionData(positionedElement, hostElement, bounds, coverTarget, doNotFinalizeReturnEdge) { var finalizedElement = _finalizeElementPosition(positionedElement.elementRectangle, hostElement, positionedElement.targetEdge, bounds, positionedElement.alignmentEdge, coverTarget, doNotFinalizeReturnEdge, positionedElement.forcedInBounds); return { elementPosition: finalizedElement, targetEdge: positionedElement.targetEdge, alignmentEdge: positionedElement.alignmentEdge, }; } function _positionElement(props, hostElement, elementToPosition, previousPositions, win) { var theWin = win !== null && win !== void 0 ? win : (0, Utilities_1.getWindow)(); var boundingRect = props.bounds ? _getRectangleFromIRect(props.bounds) : new Utilities_2.Rectangle(0, theWin.innerWidth - (0, Utilities_1.getScrollbarWidth)(), 0, theWin.innerHeight); var positionedElement = _positionElementRelative(props, elementToPosition, boundingRect, previousPositions); return _finalizePositionData(positionedElement, hostElement, boundingRect, props.coverTarget); } function _calculateGapSpace(isBeakVisible, beakWidth, gapSpace) { if (beakWidth === void 0) { beakWidth = 0; } if (gapSpace === void 0) { gapSpace = 0; } return _calculateActualBeakWidthInPixels(isBeakVisible ? beakWidth : 0) / 2 + gapSpace; } function _positionCallout(props, hostElement, callout, previousPositions, shouldScroll, minimumScrollResizeHeight, doNotFinalizeReturnEdge, win) { if (shouldScroll === void 0) { shouldScroll = false; } var theWin = win !== null && win !== void 0 ? win : (0, Utilities_1.getWindow)(); var beakWidth = props.isBeakVisible ? props.beakWidth || 0 : 0; var gap = _calculateGapSpace(props.isBeakVisible, props.beakWidth, props.gapSpace); var positionProps = props; positionProps.gapSpace = gap; var boundingRect = props.bounds ? _getRectangleFromIRect(props.bounds) : new Utilities_2.Rectangle(0, theWin.innerWidth - (0, Utilities_1.getScrollbarWidth)(), 0, theWin.innerHeight); var positionedElement = _positionElementRelative(positionProps, callout, boundingRect, previousPositions, shouldScroll, minimumScrollResizeHeight); var beakPositioned = _positionBeak(beakWidth, positionedElement); var finalizedBeakPosition = _finalizeBeakPosition(positionedElement, beakPositioned, boundingRect); return tslib_1.__assign(tslib_1.__assign({}, _finalizePositionData(positionedElement, hostElement, boundingRect, props.coverTarget, doNotFinalizeReturnEdge)), { beakPosition: finalizedBeakPosition }); } function _positionCard(props, hostElement, callout, previousPositions, win) { var theWin = win !== null && win !== void 0 ? win : (0, Utilities_1.getWindow)(); return _positionCallout(props, hostElement, callout, previousPositions, false, undefined, true, theWin); } function _getRectangleFromTarget(target) { var _a, _b, _c, _d; var mouseTarget = target; var elementTarget = target; var rectOrPointTarget = target; var targetRect; // eslint-disable-next-line @typescript-eslint/no-deprecated var left = (_a = rectOrPointTarget.left) !== null && _a !== void 0 ? _a : rectOrPointTarget.x; // eslint-disable-next-line @typescript-eslint/no-deprecated var top = (_b = rectOrPointTarget.top) !== null && _b !== void 0 ? _b : rectOrPointTarget.y; var right = (_c = rectOrPointTarget.right) !== null && _c !== void 0 ? _c : left; var bottom = (_d = rectOrPointTarget.bottom) !== null && _d !== void 0 ? _d : top; // eslint-disable-next-line no-extra-boolean-cast -- may not actually be a MouseEvent if (!!mouseTarget.stopPropagation) { targetRect = new Utilities_2.Rectangle(mouseTarget.clientX, mouseTarget.clientX, mouseTarget.clientY, mouseTarget.clientY); } else if (left !== undefined && top !== undefined) { targetRect = new Utilities_2.Rectangle(left, right, top, bottom); } else { targetRect = _getRectangleFromElement(elementTarget); } return targetRect; } // END PRIVATE FUNCTIONS exports.__positioningTestPackage = { _finalizePositionData: _finalizePositionData, _finalizeBeakPosition: _finalizeBeakPosition, _calculateActualBeakWidthInPixels: _calculateActualBeakWidthInPixels, _positionElementWithinBounds: _positionElementWithinBounds, _positionBeak: _positionBeak, _getPositionData: _getPositionData, _getMaxHeightFromTargetRectangle: _getMaxHeightFromTargetRectangle, }; /** * Used to position an element relative to the given positioning props. * If positioning has been completed before, previousPositions can be passed to ensure that the positioning element * repositions based on its previous targets rather than starting with directionalhint. */ function positionElement(props, hostElement, elementToPosition, previousPositions, win) { return _positionElement(props, hostElement, elementToPosition, previousPositions, win); } function positionCallout(props, hostElement, elementToPosition, previousPositions, shouldScroll, minimumScrollResizeHeight, win) { return _positionCallout(props, hostElement, elementToPosition, previousPositions, shouldScroll, minimumScrollResizeHeight, undefined, win); } function positionCard(props, hostElement, elementToPosition, previousPositions, win) { return _positionCard(props, hostElement, elementToPosition, previousPositions, win); } /** * Gets the maximum height that a rectangle can have in order to fit below or above a target. * If the directional hint specifies a left or right edge (i.e. leftCenter) it will limit the height to the topBorder * of the target given. * If no bounds are provided then the window is treated as the bounds. */ function getMaxHeight(target, targetEdge, gapSpace, bounds, coverTarget, win) { if (gapSpace === void 0) { gapSpace = 0; } var theWin = win !== null && win !== void 0 ? win : (0, Utilities_1.getWindow)(); var targetRect = _getRectangleFromTarget(target); var boundingRectangle = bounds ? _getRectangleFromIRect(bounds) : new Utilities_2.Rectangle(0, theWin.innerWidth - (0, Utilities_1.getScrollbarWidth)(), 0, theWin.innerHeight); return _getMaxHeightFromTargetRectangle(targetRect, targetEdge, gapSpace, boundingRectangle, coverTarget); } /** * Returns the opposite edge of the given RectangleEdge. */ function getOppositeEdge(edge) { return edge * -1; } function _getBoundsFromTargetWindow(target, targetWindow) { var segments = undefined; if (targetWindow.getWindowSegments) { segments = targetWindow.getWindowSegments(); } // Identify if we're dealing with single screen scenarios. if (segments === undefined || segments.length <= 1) { return { top: 0, left: 0, right: targetWindow.innerWidth, bottom: targetWindow.innerHeight, width: targetWindow.innerWidth, height: targetWindow.innerHeight, }; } // Logic for determining dual screen scenarios. var x = 0; var y = 0; // If the target is an Element get coordinates for its center. if (target !== null && !!target.getBoundingClientRect) { var clientRect = target.getBoundingClientRect(); x = (clientRect.left + clientRect.right) / 2; y = (clientRect.top + clientRect.bottom) / 2; } // If the target is not null get x-axis and y-axis coordinates directly. else if (target !== null) { // eslint-disable-next-line @typescript-eslint/no-deprecated x = target.left || target.x; // eslint-disable-next-line @typescript-eslint/no-deprecated y = target.top || target.y; } var bounds = { top: 0, left: 0, right: 0, bottom: 0, width: 0, height: 0 }; // Define which window segment are the coordinates in and calculate bounds based on that. for (var _i = 0, segments_1 = segments; _i < segments_1.length; _i++) { var segment = segments_1[_i]; if (x && segment.left <= x && segment.right >= x && y && segment.top <= y && segment.bottom >= y) { bounds = { top: segment.top, left: segment.left, right: segment.right, bottom: segment.bottom, width: segment.width, height: segment.height, }; } } return bounds; } function getBoundsFromTargetWindow(target, targetWindow) { return _getBoundsFromTargetWindow(target, targetWindow); } function calculateGapSpace(isBeakVisible, beakWidth, gapSpace) { return _calculateGapSpace(isBeakVisible, beakWidth, gapSpace); } function getRectangleFromTarget(target) { return _getRectangleFromTarget(target); } //# sourceMappingURL=positioning.js.map