/** * @copyright Valor Software * @copyright Angular ng-bootstrap team */ // previous version: // https://github.com/angular-ui/bootstrap/blob/07c31d0731f7cb068a1932b8e01d2312b796b4ec/src/position/position.js // tslint:disable export class Positioning { public position(element: HTMLElement, round = true): ClientRect { let elPosition: ClientRect; let parentOffset: ClientRect = { width: 0, height: 0, top: 0, bottom: 0, left: 0, right: 0 }; if (this.getStyle(element, 'position') === 'fixed') { const bcRect = element.getBoundingClientRect(); elPosition = { width: bcRect.width, height: bcRect.height, top: bcRect.top, bottom: bcRect.bottom, left: bcRect.left, right: bcRect.right }; } else { const offsetParentEl = this.offsetParent(element); elPosition = this.offset(element, false); if (offsetParentEl !== document.documentElement) { parentOffset = this.offset(offsetParentEl, false); } parentOffset.top += offsetParentEl.clientTop; parentOffset.left += offsetParentEl.clientLeft; } elPosition.top -= parentOffset.top; elPosition.bottom -= parentOffset.top; elPosition.left -= parentOffset.left; elPosition.right -= parentOffset.left; if (round) { elPosition.top = Math.round(elPosition.top); elPosition.bottom = Math.round(elPosition.bottom); elPosition.left = Math.round(elPosition.left); elPosition.right = Math.round(elPosition.right); } return elPosition; } public offset(element: HTMLElement, round = true): ClientRect { const elBcr = element.getBoundingClientRect(); const viewportOffset = { top: window.pageYOffset - document.documentElement.clientTop, left: window.pageXOffset - document.documentElement.clientLeft }; let elOffset = { height: elBcr.height || element.offsetHeight, width: elBcr.width || element.offsetWidth, top: elBcr.top + viewportOffset.top, bottom: elBcr.bottom + viewportOffset.top, left: elBcr.left + viewportOffset.left, right: elBcr.right + viewportOffset.left }; if (round) { elOffset.height = Math.round(elOffset.height); elOffset.width = Math.round(elOffset.width); elOffset.top = Math.round(elOffset.top); elOffset.bottom = Math.round(elOffset.bottom); elOffset.left = Math.round(elOffset.left); elOffset.right = Math.round(elOffset.right); } return elOffset; } public positionElements( hostElement: HTMLElement, targetElement: HTMLElement, placement: string, appendToBody?: boolean ): ClientRect { const hostElPosition = appendToBody ? this.offset(hostElement, false) : this.position(hostElement, false); const targetElStyles = this.getAllStyles(targetElement); const shiftWidth: any = { left: hostElPosition.left, center: hostElPosition.left + hostElPosition.width / 2 - targetElement.offsetWidth / 2, right: hostElPosition.left + hostElPosition.width }; const shiftHeight: any = { top: hostElPosition.top, center: hostElPosition.top + hostElPosition.height / 2 - targetElement.offsetHeight / 2, bottom: hostElPosition.top + hostElPosition.height }; const targetElBCR = targetElement.getBoundingClientRect(); let placementPrimary = placement.split(' ')[0] || 'top'; const placementSecondary = placement.split(' ')[1] || 'center'; let targetElPosition: ClientRect = { height: targetElBCR.height || targetElement.offsetHeight, width: targetElBCR.width || targetElement.offsetWidth, top: 0, bottom: targetElBCR.height || targetElement.offsetHeight, left: 0, right: targetElBCR.width || targetElement.offsetWidth }; if (placementPrimary === 'auto') { let newPlacementPrimary = this.autoPosition( targetElPosition, hostElPosition, targetElement, placementSecondary ); if (!newPlacementPrimary) newPlacementPrimary = this.autoPosition( targetElPosition, hostElPosition, targetElement ); if (newPlacementPrimary) placementPrimary = newPlacementPrimary; targetElement.classList.add(placementPrimary); } switch (placementPrimary) { case 'top': targetElPosition.top = hostElPosition.top - (targetElement.offsetHeight + parseFloat(targetElStyles.marginBottom)); targetElPosition.bottom += hostElPosition.top - targetElement.offsetHeight; targetElPosition.left = shiftWidth[placementSecondary]; targetElPosition.right += shiftWidth[placementSecondary]; break; case 'bottom': targetElPosition.top = shiftHeight[placementPrimary]; targetElPosition.bottom += shiftHeight[placementPrimary]; targetElPosition.left = shiftWidth[placementSecondary]; targetElPosition.right += shiftWidth[placementSecondary]; break; case 'left': targetElPosition.top = shiftHeight[placementSecondary]; targetElPosition.bottom += shiftHeight[placementSecondary]; targetElPosition.left = hostElPosition.left - (targetElement.offsetWidth + parseFloat(targetElStyles.marginRight)); targetElPosition.right += hostElPosition.left - targetElement.offsetWidth; break; case 'right': targetElPosition.top = shiftHeight[placementSecondary]; targetElPosition.bottom += shiftHeight[placementSecondary]; targetElPosition.left = shiftWidth[placementPrimary]; targetElPosition.right += shiftWidth[placementPrimary]; break; } targetElPosition.top = Math.round(targetElPosition.top); targetElPosition.bottom = Math.round(targetElPosition.bottom); targetElPosition.left = Math.round(targetElPosition.left); targetElPosition.right = Math.round(targetElPosition.right); return targetElPosition; } private autoPosition( targetElPosition: ClientRect, hostElPosition: ClientRect, targetElement: HTMLElement, preferredPosition?: string ) { if ( (!preferredPosition || preferredPosition === 'right') && targetElPosition.left + hostElPosition.left - targetElement.offsetWidth < 0 ) { return 'right'; } else if ( (!preferredPosition || preferredPosition === 'top') && targetElPosition.bottom + hostElPosition.bottom + targetElement.offsetHeight > window.innerHeight ) { return 'top'; } else if ( (!preferredPosition || preferredPosition === 'bottom') && targetElPosition.top + hostElPosition.top - targetElement.offsetHeight < 0 ) { return 'bottom'; } else if ( (!preferredPosition || preferredPosition === 'left') && targetElPosition.right + hostElPosition.right + targetElement.offsetWidth > window.innerWidth ) { return 'left'; } return null; } private getAllStyles(element: HTMLElement) { return window.getComputedStyle(element); } private getStyle(element: HTMLElement, prop: string): string { return (this.getAllStyles(element) as any)[prop]; } private isStaticPositioned(element: HTMLElement): boolean { return (this.getStyle(element, 'position') || 'static') === 'static'; } private offsetParent(element: HTMLElement): HTMLElement { let offsetParentEl = element.offsetParent || document.documentElement; while ( offsetParentEl && offsetParentEl !== document.documentElement && this.isStaticPositioned(offsetParentEl) ) { offsetParentEl = offsetParentEl.offsetParent; } return offsetParentEl || document.documentElement; } } const positionService = new Positioning(); export function positionElements( hostElement: HTMLElement, targetElement: HTMLElement, placement: string, appendToBody?: boolean ): void { const pos = positionService.positionElements( hostElement, targetElement, placement, appendToBody ); targetElement.style.top = `${pos.top}px`; targetElement.style.left = `${pos.left}px`; }