UNPKG

@exadel/esl

Version:

Exadel Smart Library (ESL) is the lightweight custom elements library that provide a set of super-flexible components

213 lines (212 loc) 8.86 kB
import { Rect } from '../../esl-utils/dom/rect'; /** * Checks that the position along the horizontal axis * @param position - name of position */ export function isOnHorizontalAxis(position) { return ['left', 'right'].includes(position); } /** * Checks whether the specified position corresponds to the starting side * @param position - name of position */ function isStartingSide(position) { return ['left', 'top'].includes(position); } /** * Gets the name of the dimension along the axis of the specified position * @param position - name of position * @param alter - should it be the opposite dimension? */ function getDimensionName(position, alter = false) { const isHorizontal = isOnHorizontalAxis(position); return (alter ? !isHorizontal : isHorizontal) ? 'width' : 'height'; } /** * Gets the name of the position where the arrow should be placed * @param cfg - popup position config * @param isOpposite - should it be the opposite position? */ function getPlacedAt(cfg, isOpposite = false) { const position = isOpposite ? getOppositePosition(cfg.position) : cfg.position; return `${position}${cfg.hasInnerOrigin ? '-inner' : ''}`; } /** * Calculates the position of the popup on the major axis * @param cfg - popup position config */ function calcPopupPositionByMajorAxis(cfg) { const { position, inner, element, hasInnerOrigin } = cfg; const coord = inner[position]; const size = element[getDimensionName(position)]; return isStartingSide(position) ? (hasInnerOrigin ? coord : coord - size) : (hasInnerOrigin ? coord - size : coord); } /** * Calculates the position of the popup on the minor axis * @param cfg - popup position config */ function calcPopupPositionByMinorAxis(cfg) { const { position, inner, arrow, marginArrow, offsetArrowRatio } = cfg; const centerPosition = inner[isOnHorizontalAxis(position) ? 'cy' : 'cx']; const dimensionName = getDimensionName(position, true); return centerPosition - arrow[dimensionName] / 2 - marginArrow - calcUsableSizeForArrow(cfg, dimensionName) * offsetArrowRatio; } /** * Calculates Rect for given popup position config. * @param cfg - popup position config * */ function calcPopupBasicRect(cfg) { const { width, height } = cfg.element; const coordForMajor = calcPopupPositionByMajorAxis(cfg); const coordForMinor = calcPopupPositionByMinorAxis(cfg); return isOnHorizontalAxis(cfg.position) ? new Rect(coordForMajor, coordForMinor, width, height) : new Rect(coordForMinor, coordForMajor, width, height); } /** * Calculates position for all sub-parts of popup for given popup position config. * @param cfg - popup position config * */ function calcBasicPosition(cfg) { const popup = calcPopupBasicRect(cfg); const arrow = { x: calcArrowPosition(cfg, 'width'), y: calcArrowPosition(cfg, 'height'), }; return { arrow, popup, placedAt: getPlacedAt(cfg) }; } /** * Gets opposite position. * @param position - name of position * */ function getOppositePosition(position) { return ({ top: 'bottom', left: 'right', right: 'left', bottom: 'top' }[position] || position); } /** * Checks and updates popup and arrow positions to fit on major axis. * @param cfg - popup position config * @param value - current popup's position value * @returns updated popup position value * */ function fitOnMajorAxis(cfg, value) { if (!['fit', 'fit-major'].includes(cfg.behavior)) return value; const { position, hasInnerOrigin, outer } = cfg; const intersectionRatio = cfg.intersectionRatio[hasInnerOrigin ? getOppositePosition(position) : position] || 0; const valueToCheck = hasInnerOrigin ? cfg.inner : value.popup; const isComingOut = isStartingSide(position) ? valueToCheck[position] < outer[position] : outer[position] < valueToCheck[position]; const isRequireAdjusting = hasInnerOrigin ? intersectionRatio === 0 && isComingOut : intersectionRatio > 0 || isComingOut; return isRequireAdjusting ? adjustAlongMajorAxis(cfg, value) : value; } /** * Updates popup and arrow positions to fit on major axis. * @param cfg - popup position config * @param value - current popup's position value * @returns updated popup position value * */ function adjustAlongMajorAxis(cfg, value) { const oppositeConfig = Object.assign(Object.assign({}, cfg), { position: getOppositePosition(cfg.position) }); const { x, y, width, height } = value.popup; const adjustedCoord = calcPopupPositionByMajorAxis(oppositeConfig); const popup = isOnHorizontalAxis(cfg.position) ? new Rect(adjustedCoord, y, width, height) : new Rect(x, adjustedCoord, width, height); return Object.assign(Object.assign({}, value), { popup, placedAt: getPlacedAt(cfg, true) }); } /** * Calculates adjust for popup position to fit container bounds * @param cfg - popup position config * @param diffCoord - distance between the popup and the outer (container) bounding * @param arrowCoord - coordinate of the arrow * @param isStart - should it rely on the starting side? * @returns adjustment value for the coordinates of the arrow and the popup */ function adjustAlignmentBySide(cfg, diffCoord, arrowCoord, isStart) { let arrowAdjust = 0; if (isStart ? diffCoord < 0 : diffCoord > 0) { arrowAdjust = diffCoord; const newCoord = arrowCoord + arrowAdjust; const dimension = getDimensionName(cfg.position, true); const arrowLimit = cfg.marginArrow + (isStart ? 0 : calcUsableSizeForArrow(cfg, dimension)); if (isStart ? newCoord < arrowLimit : newCoord > arrowLimit) { arrowAdjust -= newCoord - arrowLimit; } } return arrowAdjust; } /** * Sets up the configuration for adjusting position along the minor axis * @param cfg - popup position config * @param popup - current popup's position value * @returns configuration for adjusting position along the minor axis */ function setupAlignmentBySide(cfg, popup) { const isHorizontal = isOnHorizontalAxis(cfg.position); const start = isHorizontal ? 'y' : 'x'; const end = isHorizontal ? 'bottom' : 'right'; const dimension = getDimensionName(cfg.position, true); const isOutAtStart = popup[start] < cfg.outer[start]; const isOutAtEnd = popup[end] > cfg.outer[end]; const isWider = cfg.outer[dimension] < cfg.element[dimension]; return { isHorizontal, start, end, isOutAtStart, isOutAtEnd, isWider }; } /** * Updates popup and arrow positions to fit on minor axis. * @param cfg - popup position config * @param value - current popup's position value * @returns updated popup position value * */ function fitOnMinorAxis(cfg, value) { if (!['fit', 'fit-minor'].includes(cfg.behavior)) return value; const { popup, arrow } = value; const { isHorizontal, start, end, isOutAtStart, isOutAtEnd, isWider } = setupAlignmentBySide(cfg, popup); // nothing to do when there is no outing if (!isOutAtStart && !isOutAtEnd) return value; // start-side adjusting happens if there is only start-side outing or LTR content direction const isStarting = isOutAtStart && (!isOutAtEnd || !cfg.isRTL); // the side for calculating the distance between the popup and the outer (container) bounding should be: // - when the popup is wider than the container the diff side should depend on the text direction // (start side for LTR, end side for RTL) // - else we should choose start side if start-side outing or end side if end-side outing const diffSide = (isWider ? !cfg.isRTL : isStarting) ? start : end; const diff = popup[diffSide] - cfg.outer[diffSide]; const shift = adjustAlignmentBySide(cfg, diff, arrow[start], isStarting); arrow[start] += shift; return Object.assign(Object.assign({}, value), { popup: isHorizontal ? popup.shift(0, -shift) : popup.shift(-shift, 0), arrow }); } /** * Calculates the usable size available for the arrow * @param cfg - popup position config * @param dimensionName - the name of dimension (height or width) */ function calcUsableSizeForArrow(cfg, dimensionName) { return cfg.element[dimensionName] - cfg.arrow[dimensionName] - 2 * cfg.marginArrow; } /** * Calculates the position of the arrow on the minor axis * @param cfg - popup position config * @param dimensionName - the name of dimension (height or width) */ function calcArrowPosition(cfg, dimensionName) { return cfg.marginArrow + calcUsableSizeForArrow(cfg, dimensionName) * cfg.offsetArrowRatio; } /** * Calculates popup and arrow popup positions. * @param cfg - popup position config * */ export function calcPopupPosition(cfg) { return fitOnMinorAxis(cfg, fitOnMajorAxis(cfg, calcBasicPosition(cfg))); }