kamrify's picture
Popover auto positioning
ec45071
raw
history blame
4.03 kB
import Position from './position';
export default class Element {
/**
* DOM element object
* @param node
* @param options
*/
constructor(node, options) {
this.node = node;
this.document = document;
this.window = window;
this.options = options;
this.popover = this.getPopover();
}
/**
* Gets the screen co-ordinates (x,y) for the current dom element
* @returns {{x: number, y: number}}
*/
getScreenCoordinates() {
let tempNode = this.node;
let x = this.document.documentElement.offsetLeft;
let y = this.document.documentElement.offsetTop;
if (tempNode.offsetParent) {
do {
x += tempNode.offsetLeft;
y += tempNode.offsetTop;
} while (tempNode = tempNode.offsetParent);
}
return { x, y };
}
/**
* Gets the calculated position on screen, around which
* we need to draw
*/
getCalculatedPosition() {
const coordinates = this.getScreenCoordinates();
const position = new Position({
left: Number.MAX_VALUE,
top: Number.MAX_VALUE,
right: 0,
bottom: 0,
});
// If we have the position for this element
// and the element is visible on screen (has some height)
if (typeof coordinates.x === 'number' && typeof coordinates.y === 'number' && (this.node.offsetWidth > 0 || this.node.offsetHeight > 0)) {
position.left = Math.min(position.left, coordinates.x);
position.top = Math.min(position.top, coordinates.y);
position.right = Math.max(position.right, coordinates.x + this.node.offsetWidth);
position.bottom = Math.max(position.bottom, coordinates.y + this.node.offsetHeight);
}
return position;
}
onDeselected() {
// Will be called when element is about to be deselected
this.hidePopover();
}
onHighlightStarted() {
// Will be triggered when the element is about to be highlighted
// i.e. overlay has started transitioning towards this element
this.showPopover();
}
onHighlighted() {
this.showPopover();
}
showPopover() {
this.resetPopover();
// Position at which the element is
const position = this.getCalculatedPosition();
const popoverTip = this.popover.querySelector('.sholo-popover-tip');
const documentHeight = this.getDocumentHeight();
const popoverHeight = this.getPopoverHeight();
const popoverMargin = this.options.padding + 10;
this.popover.style.left = `${position.left - this.options.padding}px`;
// Calculate different dimensions after attaching popover
const documentHeightAfterPopOver = position.bottom + popoverHeight + popoverMargin;
// If adding popover would go out of the window height, then show it to the top
if (documentHeightAfterPopOver >= documentHeight) {
this.popover.style.top = `${position.top - popoverHeight - popoverMargin}px`;
popoverTip.classList.add('bottom');
} else {
this.popover.style.top = `${position.bottom + popoverMargin}px`;
popoverTip.classList.add('top');
}
}
getPopover() {
// @todo: Create if not there
const popover = this.document.getElementById('sholo-popover-item');
popover.style.position = 'absolute';
return popover;
}
hidePopover() {
this.popover.style.display = 'none';
}
getDocumentHeight() {
// eslint-disable-next-line prefer-destructuring
const body = this.document.body;
const html = this.document.documentElement;
return Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight);
}
getPopoverHeight() {
return Math.max(this.popover.scrollHeight, this.popover.offsetHeight);
}
resetPopover() {
this.popover.style.display = 'block';
this.popover.style.left = '';
this.popover.style.top = '';
this.popover.style.bottom = '';
this.popover.style.right = '';
// Remove the positional classes from tip
this.popover
.querySelector('.sholo-popover-tip')
.className = 'sholo-popover-tip';
}
}