@syncfusion/ej2-popups
Version:
A package of Essential JS 2 popup components such as Dialog and Tooltip that is used to display information or messages in separate pop-ups.
517 lines (510 loc) • 17.7 kB
text/typescript
/**
* Collision module.
*/
import { calculatePosition, OffsetPosition } from './position';
import { isNullOrUndefined } from '@syncfusion/ej2-base';
interface TopCorners {
topSide: boolean
bottomSide: boolean
}
interface LeftCorners {
leftSide: boolean
rightSide: boolean
}
interface RectData {
width: number
height: number
}
//eslint-disable-next-line
interface ElementsData {
elementData: RectData
}
interface EdgeOffset {
TL: OffsetPosition
TR: OffsetPosition
BL: OffsetPosition
BR: OffsetPosition
}
interface PositionLocation {
posX: string
posY: string
offsetX: number
offsetY: number
position: OffsetPosition
}
let parentDocument: Document;
let targetContainer: HTMLElement;
/**
* Provides information about a CollisionCoordinates.
*/
export interface CollisionCoordinates {
X: boolean
Y: boolean
}
/**
*
* @param {HTMLElement} element - specifies the element
* @param {HTMLElement} viewPortElement - specifies the element
* @param {CollisionCoordinates} axis - specifies the collision coordinates
* @param {OffsetPosition} position - specifies the position
* @returns {void}
*/
export function fit(
element: HTMLElement,
viewPortElement: HTMLElement = null,
axis: CollisionCoordinates = {X: false, Y: false},
position?: OffsetPosition): OffsetPosition {
if (!axis.Y && !axis.X) {
return {left: 0, top: 0};
}
const elemData: ClientRect = element.getBoundingClientRect();
targetContainer = viewPortElement;
parentDocument = element.ownerDocument;
if (!position) {
position = calculatePosition(element, 'left', 'top');
}
if (axis.X) {
const containerWidth: number = targetContainer ? getTargetContainerWidth() : getViewPortWidth();
const containerLeft: number = ContainerLeft();
const containerRight: number = ContainerRight();
const overLeft: number = containerLeft - position.left;
const overRight: number = position.left + elemData.width - containerRight;
if (elemData.width > containerWidth) {
if (overLeft > 0 && overRight <= 0) {
position.left = containerRight - elemData.width;
} else if (overRight > 0 && overLeft <= 0) {
position.left = containerLeft;
} else {
position.left = overLeft > overRight ? (containerRight - elemData.width) : containerLeft;
}
} else if (overLeft > 0) {
position.left += overLeft;
} else if (overRight > 0) {
position.left -= overRight;
}
}
if (axis.Y) {
const containerHeight: number = targetContainer ? getTargetContainerHeight() : getViewPortHeight();
const containerTop: number = ContainerTop();
const containerBottom: number = ContainerBottom();
const overTop: number = containerTop - position.top;
const overBottom: number = position.top + elemData.height - containerBottom;
if (elemData.height > containerHeight) {
if (overTop > 0 && overBottom <= 0) {
position.top = containerBottom - elemData.height;
} else if (overBottom > 0 && overTop <= 0) {
position.top = containerTop;
} else {
position.top = overTop > overBottom ? (containerBottom - elemData.height) : containerTop;
}
} else if (overTop > 0) {
position.top += overTop;
} else if (overBottom > 0) {
position.top -= overBottom;
}
}
return position;
}
/**
*
* @param {HTMLElement} element - specifies the html element
* @param {HTMLElement} viewPortElement - specifies the html element
* @param {number} x - specifies the number
* @param {number} y - specifies the number
* @returns {string[]} - returns the string value
*/
export function isCollide(element: HTMLElement, viewPortElement: HTMLElement = null, x?: number, y?: number): string[] {
const elemOffset: OffsetPosition = calculatePosition(element, 'left', 'top');
if (x) {
elemOffset.left = x;
}
if (y) {
elemOffset.top = y;
}
const data: string[] = [];
targetContainer = viewPortElement;
parentDocument = element.ownerDocument;
const elementRect: ClientRect = element.getBoundingClientRect();
const top: number = elemOffset.top;
const left: number = elemOffset.left;
const right: number = elemOffset.left + elementRect.width;
const bottom: number = elemOffset.top + elementRect.height;
const yAxis: TopCorners = topCollideCheck(top, bottom);
const xAxis: LeftCorners = leftCollideCheck(left, right);
if (yAxis.topSide) {
data.push('top');
}
if (xAxis.rightSide) {
data.push('right');
}
if (xAxis.leftSide) {
data.push('left');
}
if (yAxis.bottomSide) {
data.push('bottom');
}
return data;
}
/**
*
* @param {HTMLElement} element - specifies the element
* @param {HTMLElement} target - specifies the element
* @param {number} offsetX - specifies the number
* @param {number} offsetY - specifies the number
* @param {string} positionX - specifies the string value
* @param {string} positionY - specifies the string value
* @param {HTMLElement} viewPortElement - specifies the element
* @param {CollisionCoordinates} axis - specifies the collision axis
* @param {boolean} fixedParent - specifies the boolean
* @returns {void}
*/
export function flip(
element: HTMLElement,
target: HTMLElement,
offsetX: number,
offsetY: number,
positionX: string,
positionY: string,
viewPortElement: HTMLElement = null,
/* eslint-disable */
axis: CollisionCoordinates = {X: true, Y: true},
fixedParent?: boolean): void {
if (!target || !element || !positionX || !positionY || (!axis.X && !axis.Y)) {
return;
}
const tEdge: EdgeOffset = { TL: null,
TR: null,
BL: null,
BR: null
}, eEdge: EdgeOffset = {
TL: null,
TR: null,
BL: null,
BR: null
/* eslint-enable */
};
let elementRect: ClientRect;
if (window.getComputedStyle(element).display === 'none') {
const oldVisibility: string = element.style.visibility;
element.style.visibility = 'hidden';
element.style.display = 'block';
elementRect = element.getBoundingClientRect();
element.style.removeProperty('display');
element.style.visibility = oldVisibility;
} else {
elementRect = element.getBoundingClientRect();
}
const pos: PositionLocation = {
posX: positionX, posY: positionY, offsetX: offsetX, offsetY: offsetY, position: {left: 0, top: 0}};
targetContainer = viewPortElement;
parentDocument = target.ownerDocument;
updateElementData(target, tEdge, pos, fixedParent, elementRect);
setPosition(eEdge, pos, elementRect);
if (axis.X) {
leftFlip(target, eEdge, tEdge, pos, elementRect, true);
}
if (axis.Y && tEdge.TL.top > -1) {
topFlip(target, eEdge, tEdge, pos, elementRect, true);
}
setPopup(element, pos, elementRect);
}
/**
*
* @param {HTMLElement} element - specifies the element
* @param {PositionLocation} pos - specifies the location
* @param {ClientRect} elementRect - specifies the client rect
* @returns {void}
*/
function setPopup(element: HTMLElement, pos: PositionLocation, elementRect: ClientRect): void {
let left: number = 0; let top: number = 0;
if (element.offsetParent != null
&& (getComputedStyle(element.offsetParent).position === 'absolute' ||
getComputedStyle(element.offsetParent).position === 'relative' )) {
const data: OffsetPosition = calculatePosition(element.offsetParent, 'left', 'top', false, elementRect);
left = data.left;
top = data.top;
}
let scaleX: number = 1;
let scaleY: number = 1;
const tranformElement: HTMLElement = getTransformElement(element);
if (tranformElement) {
const transformStyle: string = getComputedStyle(tranformElement).transform;
if (transformStyle !== 'none') {
const matrix: DOMMatrix = new DOMMatrix(transformStyle);
scaleX = matrix.a;
scaleY = matrix.d;
}
const zoomStyle: string = getComputedStyle(tranformElement).zoom;
if (zoomStyle !== 'none') {
const bodyZoom: number = getZoomValue(document.body);
scaleX = bodyZoom * scaleX;
scaleY = bodyZoom * scaleY;
}
}
element.style.top = ((pos.position.top / scaleY) + pos.offsetY - (top / scaleY)) + 'px';
element.style.left = ((pos.position.left / scaleX) + pos.offsetX - (left / scaleX)) + 'px';
}
export function getZoomValue(element: HTMLElement): number {
const zoomValue: string = getComputedStyle(element).zoom;
return parseFloat(zoomValue) || 1; // Default zoom value is 1 (no zoom)
}
/**
*
* @param {HTMLElement} element - specifies the element
* @returns {HTMLElement} The modified element.
*/
export function getTransformElement(element: HTMLElement): HTMLElement {
while (element) {
const transform: string = window.getComputedStyle(element).transform;
const zoom: number = getZoomValue(document.body);
if ((transform && transform !== 'none') || (zoom && zoom !== 1)) {
return element;
}
if (element === document.body) {
return null;
}
element = (element.offsetParent || element.parentElement) as HTMLElement;
}
return null;
}
/**
*
* @param {HTMLElement} target - specifies the element
* @param {EdgeOffset} edge - specifies the offset
* @param {PositionLocation} pos - specifies theloaction
* @param {boolean} fixedParent - specifies the boolean
* @param {ClientRect} elementRect - specifies the client rect
* @returns {void}
*/
function updateElementData(
target: HTMLElement,
edge: EdgeOffset,
pos: PositionLocation,
fixedParent: boolean,
elementRect: ClientRect): void {
pos.position = calculatePosition(target, pos.posX, pos.posY, fixedParent, elementRect);
edge.TL = calculatePosition(target, 'left', 'top', fixedParent, elementRect);
edge.TR = calculatePosition(target, 'right', 'top', fixedParent, elementRect);
edge.BR = calculatePosition(target, 'left', 'bottom', fixedParent, elementRect);
edge.BL = calculatePosition(target, 'right', 'bottom', fixedParent, elementRect);
}
/**
*
* @param {EdgeOffset} eStatus - specifies the status
* @param {PositionLocation} pos - specifies the location
* @param {ClientRect} elementRect - specifies the client
* @returns {void}
*/
function setPosition(
eStatus: EdgeOffset,
pos: PositionLocation,
elementRect: ClientRect): void {
eStatus.TL = { top: pos.position.top + pos.offsetY, left: pos.position.left + pos.offsetX };
eStatus.TR = { top: eStatus.TL.top, left: eStatus.TL.left + elementRect.width };
eStatus.BL = { top: eStatus.TL.top + elementRect.height,
left: eStatus.TL.left };
eStatus.BR = { top: eStatus.TL.top + elementRect.height
, left: eStatus.TL.left + elementRect.width };
}
/**
*
* @param {number} left - specifies the number
* @param {number} right - specifies the number
* @returns {LeftCorners} - returns the value
*/
function leftCollideCheck(left: number, right: number): LeftCorners {
//eslint-disable-next-line
let leftSide: boolean = false, rightSide: boolean = false;
if (((left - getBodyScrollLeft()) < ContainerLeft())) {
leftSide = true;
}
if (right > ContainerRight()) {
rightSide = true;
}
return {leftSide: leftSide, rightSide: rightSide};
}
/**
*
* @param {HTMLElement} target - specifies the element
* @param {EdgeOffset} edge - specifes the element
* @param {EdgeOffset} tEdge - specifies the edge offset
* @param {PositionLocation} pos - specifes the location
* @param {ClientRect} elementRect - specifies the client
* @param {boolean} deepCheck - specifies the boolean value
* @returns {void}
*/
function leftFlip(
target: HTMLElement,
edge: EdgeOffset,
tEdge: EdgeOffset,
pos: PositionLocation,
elementRect: ClientRect,
deepCheck: boolean): void {
const collideSide: LeftCorners = leftCollideCheck(edge.TL.left, edge.TR.left);
if ((tEdge.TL.left - getBodyScrollLeft()) <= ContainerLeft()) {
collideSide.leftSide = false;
}
if (tEdge.TR.left > ContainerRight()) {
collideSide.rightSide = false;
}
if ((collideSide.leftSide && !collideSide.rightSide) || (!collideSide.leftSide && collideSide.rightSide)) {
if (pos.posX === 'right') {
pos.posX = 'left';
} else {
pos.posX = 'right';
}
pos.offsetX = pos.offsetX + elementRect.width;
pos.offsetX = -1 * pos.offsetX;
pos.position = calculatePosition(target, pos.posX, pos.posY, false);
setPosition(edge, pos, elementRect);
if (deepCheck) {
leftFlip(target, edge, tEdge, pos, elementRect, false);
}
}
}
/**
*
* @param {HTMLElement} target - specifies the element
* @param {EdgeOffset} edge - specifies the offset
* @param {EdgeOffset} tEdge - specifies the offset
* @param {PositionLocation} pos - specifies the location
* @param {ClientRect} elementRect - specifies the client rect
* @param {boolean} deepCheck - specifies the boolean
* @returns {void}
*/
function topFlip(
target: HTMLElement,
edge: EdgeOffset,
tEdge: EdgeOffset,
pos: PositionLocation,
elementRect: ClientRect,
deepCheck: boolean): void {
const collideSide: TopCorners = topCollideCheck(edge.TL.top, edge.BL.top);
if ((tEdge.TL.top - getBodyScrollTop()) <= ContainerTop()) {
collideSide.topSide = false;
}
if (tEdge.BL.top >= ContainerBottom() && target.getBoundingClientRect().bottom < window.innerHeight) {
collideSide.bottomSide = false;
}
if ((collideSide.topSide && !collideSide.bottomSide) || (!collideSide.topSide && collideSide.bottomSide)) {
if (pos.posY === 'top') {
pos.posY = 'bottom';
} else {
pos.posY = 'top';
}
pos.offsetY = pos.offsetY + elementRect.height;
pos.offsetY = -1 * pos.offsetY;
pos.position = calculatePosition(target, pos.posX, pos.posY, false, elementRect);
setPosition(edge, pos, elementRect);
if (deepCheck) {
topFlip(target, edge, tEdge, pos, elementRect, false);
}
}
}
/**
*
* @param {number} top - specifies the number
* @param {number} bottom - specifies the number
* @returns {TopCorners} - retyrns the value
*/
function topCollideCheck(top: number, bottom: number): TopCorners {
//eslint-disable-next-line
let topSide: boolean = false, bottomSide: boolean = false;
if ((top - getBodyScrollTop()) < ContainerTop()) {
topSide = true;
}
if (bottom > ContainerBottom()) {
bottomSide = true;
}
return { topSide: topSide, bottomSide: bottomSide };
}
/**
* @returns {void}
*/
function getTargetContainerWidth(): number {
return targetContainer.getBoundingClientRect().width;
}
/**
* @returns {void}
*/
function getTargetContainerHeight(): number {
return targetContainer.getBoundingClientRect().height;
}
/**
* @returns {void}
*/
function getTargetContainerLeft(): number {
return targetContainer.getBoundingClientRect().left;
}
/**
* @returns {void}
*/
function getTargetContainerTop(): number {
return targetContainer.getBoundingClientRect().top;
}
//eslint-disable-next-line
function ContainerTop(): number {
if (targetContainer) {
return getTargetContainerTop();
}
return 0;
}
//eslint-disable-next-line
function ContainerLeft(): number {
if (targetContainer) {
return getTargetContainerLeft();
}
return 0;
}
//eslint-disable-next-line
function ContainerRight(): number {
if (targetContainer) {
return (getBodyScrollLeft() + getTargetContainerLeft() + getTargetContainerWidth());
}
return (getBodyScrollLeft() + getViewPortWidth());
}
//eslint-disable-next-line
function ContainerBottom(): number {
if (targetContainer) {
return (getBodyScrollTop() + getTargetContainerTop() + getTargetContainerHeight());
}
return (getBodyScrollTop() + getViewPortHeight());
}
/**
* @returns {number} - returns the scroll top value
*/
function getBodyScrollTop(): number {
// if(targetContainer)
// return targetContainer.scrollTop;
return parentDocument.documentElement.scrollTop || parentDocument.body.scrollTop;
}
/**
* @returns {number} - returns the scroll left value
*/
function getBodyScrollLeft(): number {
// if(targetContainer)
// return targetContainer.scrollLeft;
return parentDocument.documentElement.scrollLeft || parentDocument.body.scrollLeft;
}
/**
* @returns {number} - returns the viewport height
*/
function getViewPortHeight(): number {
return window.innerHeight;
}
/**
* @returns {number} - returns the viewport width
*/
function getViewPortWidth(): number {
const windowWidth : number = window.innerWidth;
const documentReact: ClientRect = document.documentElement.getBoundingClientRect();
const offsetWidth: number = (isNullOrUndefined(document.documentElement)) ? 0 : documentReact.width;
return windowWidth - (windowWidth - offsetWidth);
}
/**
* @returns {void}
*/
export function destroy(): void {
targetContainer = null;
parentDocument = null;
}