/**
* @module orient
* @desc The orient module provides functions to determine the optimum orientation of a tooltip
* if auto-sizing is set to true, and to set the proper CSS values for the tooltip's div elements.
*/
import {ttDiv, beforeRule, afterRule} from './init.js';
import {windowHeight, windowWidth, getElementCoordinates, overlap, parseSize} from './utils.js';
/**
* @function position
* @desc Sets the position of the tooltip relative to the HTML element with which it is associated.
* @param {string} targetElement The unique id of the HTML element that owns the tooltip.
* @param {Tip} target The object containing all of the current tooltip's options and content.
*/
export function position (targetElement, target, orientation) {
const targetCoordinates = getElementCoordinates(targetElement);
const divWidth = ttDiv.getBoundingClientRect()['width'];
const divHeight = ttDiv.getBoundingClientRect()['height'];
const halfDivHeight = divHeight / 2;
const halfDivWidth = divWidth / 2;
const borderRadius = parseSize(target.borderRadius(), 'width', ttDiv);
const arrowSize = parseSize(target.arrowSize(), 'width', ttDiv);
afterRule.borderWidth = arrowSize + 'px';
let top;
let left;
let verticalAdjust;
let horizontalAdjust;
let sizeAdjust;
let adjustVertical = function (top) {
let topAdjust = top;
let arrowAdjust = halfDivHeight;
if (top < 0) {
topAdjust = 0;
arrowAdjust = Math.max(arrowSize + borderRadius, top + halfDivHeight);
} else if (top + divHeight > windowHeight) {
topAdjust = windowHeight - divHeight;
arrowAdjust = Math.min(divHeight - borderRadius - arrowSize, halfDivHeight + top - topAdjust);
};
return {topAdjust: Math.round(topAdjust), arrowAdjust: Math.round(arrowAdjust)};
}
let adjustHorizontal = function (left) {
let leftAdjust = left;
let arrowAdjust = halfDivWidth;
if (left < 0) {
leftAdjust = 0;
arrowAdjust = Math.max(arrowSize + borderRadius, left + halfDivWidth);
} else if (left + divWidth > windowWidth) {
leftAdjust = windowWidth - divWidth;
arrowAdjust = Math.min(divWidth - borderRadius - arrowSize, halfDivWidth + left - leftAdjust);
};
return {leftAdjust: Math.round(leftAdjust), arrowAdjust: Math.round(arrowAdjust)};
}
switch (orientation) {
case 'top': {
top = targetCoordinates.top - arrowSize - divHeight;
if (top < 0) {
beforeRule.height = Math.round(divHeight + top) + 'px';
top = 0;
};
left = (targetCoordinates.width / 2) + targetCoordinates.left - halfDivWidth;
horizontalAdjust = adjustHorizontal(left);
beforeRule.top = Math.round(top) + 'px';
beforeRule.left = horizontalAdjust.leftAdjust + 'px';
afterRule.top = '99.5%'; // '100%';
afterRule.left = horizontalAdjust.arrowAdjust + 'px';
afterRule.bottom = '';
afterRule.right = '';
afterRule.marginLeft = -arrowSize + 'px';
afterRule.marginTop = '';
afterRule.borderColor = target.backgroundColor() + ' transparent transparent transparent';
break;
};
case 'bottom': {
top = targetCoordinates.top + targetCoordinates.height + arrowSize;
sizeAdjust = windowHeight - divHeight + top + arrowSize;
beforeRule.height = (sizeAdjust < 0) ? (divHeight + sizeAdjust) + 'px' : beforeRule.height;
left = (targetCoordinates.width / 2) + targetCoordinates.left - halfDivWidth;
horizontalAdjust = adjustHorizontal(left);
beforeRule.top = Math.round(top) + 'px';
beforeRule.left = horizontalAdjust.leftAdjust + 'px';
afterRule.top = '';
afterRule.left = horizontalAdjust.arrowAdjust + 'px';
afterRule.bottom = '99.5%'; //'100%';
afterRule.right = '';
afterRule.marginLeft = -arrowSize + 'px';
afterRule.marginTop = '';
afterRule.borderColor = 'transparent transparent ' + target.backgroundColor() + ' transparent';
break;
};
case 'left': {
top = (targetCoordinates.height / 2) + targetCoordinates.top - halfDivHeight;
left = targetCoordinates.left - divWidth - arrowSize;
if (left < 0) {
beforeRule.width = Math.round(divWidth + left) + 'px';
left = 0;
};
verticalAdjust = adjustVertical(top);
beforeRule.top = verticalAdjust.topAdjust + 'px';
beforeRule.left = Math.round(left) + 'px';
afterRule.top = verticalAdjust.arrowAdjust + 'px';
afterRule.left = '99.5%'; //'100%';
afterRule.bottom = '';
afterRule.right = '';
afterRule.marginLeft = '';
afterRule.marginTop = -arrowSize + 'px';
afterRule.borderColor = 'transparent transparent transparent ' + target.backgroundColor();
break;
};
case 'right': {
top = (targetCoordinates.height / 2) + targetCoordinates.top - halfDivHeight;
left = targetCoordinates.left + targetCoordinates.width + arrowSize;
sizeAdjust = windowWidth - divWidth + left + arrowSize;
beforeRule.width = (sizeAdjust < 0) ? (divWidth + sizeAdjust) + 'px' : beforeRule.width;
verticalAdjust = adjustVertical(top);
beforeRule.top = verticalAdjust.topAdjust + 'px';
beforeRule.left = Math.round(left) + 'px';
afterRule.top = verticalAdjust.arrowAdjust + 'px';
afterRule.left = '';
afterRule.bottom = '';
afterRule.right = '99.5%'; //'100%';
afterRule.marginLeft = '';
afterRule.marginTop = -arrowSize + 'px';
afterRule.borderColor = 'transparent ' + target.backgroundColor() + ' transparent transparent';
break;
};
};
}
/**
* @function optimumOrientation
* @desc Determines the best side upon which to place the tooltip.
* If [auto-positioning]{@link Tip#autoPosition} is on, the
* [preferred-orientation]{@link Tip#prefferedOrientation}
* setting will be honored unless there is insufficient viewport space.
* in this case, the position with the most available space will be used.
* @param {string} targetElement The DOM element that is associated with the tooltip.
* @param {Tip} target The [Tip]{@link Tip} class object containing all of the current tooltip's options and content.
* @returns {string} One of ['left' | 'right' | 'top' | 'bottom'].
* @since v2.2.0
*/
export function optimumOrientation (targetElement, target) {
const elementCoordinates = getElementCoordinates(targetElement);
const arrowSize = parseSize(target.arrowSize(), 'width', ttDiv);
const midX = elementCoordinates.left + (elementCoordinates.width / 2);
const midY = elementCoordinates.top + (elementCoordinates.height / 2);
const divWidth = ttDiv.getBoundingClientRect()['width'];
const divHeight = ttDiv.getBoundingClientRect()['height'];
const halfDivHeight = divHeight / 2;
const halfDivWidth = divWidth / 2;
const leftOverlap = overlap ('left',
{
x0: elementCoordinates.left - arrowSize - divWidth,
x1: elementCoordinates.left - arrowSize,
y0: midY - halfDivHeight,
y1: midY + halfDivHeight
});
const rightOverlap = overlap ('right',
{
x0: elementCoordinates.left + elementCoordinates.width + arrowSize,
x1: elementCoordinates.left + elementCoordinates.width + arrowSize + divWidth,
y0: midY - halfDivHeight,
y1: midY + halfDivHeight
});
const topOverlap = overlap ('top',
{
x0: midX - halfDivWidth,
x1: midX + halfDivWidth,
y0: elementCoordinates.top - arrowSize - divHeight,
y1: elementCoordinates.top - arrowSize
});
const bottomOverlap = overlap ('bottom',
{
x0: midX - halfDivWidth,
x1: midX + halfDivWidth,
y0: elementCoordinates.top + elementCoordinates.height + arrowSize,
y1: elementCoordinates.top + elementCoordinates.height + arrowSize + divHeight
});
switch (target.preferredOrientation()) {
case 'left': { if (leftOverlap.overlap == 1) return 'left'; break; };
case 'right': { if (rightOverlap.overlap == 1) return 'right'; break; };
case 'top': { if (topOverlap.overlap == 1) return 'top'; break; };
case 'bottom': { if (bottomOverlap.overlap == 1) return 'bottom'; break; };
};
// if there is no preferred orientation or all overlaps are less than 1
let overlaps = [leftOverlap, rightOverlap, topOverlap, bottomOverlap];
//if all of the overlaps are less than 1 return the greatest
if (leftOverlap.overlap < 1 && rightOverlap.overlap < 1 && topOverlap.overlap < 1 && bottomOverlap.overlap < 1) {
return overlaps[overlaps.reduce((prev, current, index, array) => {
if (current.overlap > array[prev].overlap) {return index} else {return prev};
}, 0)].side;
};
// remove all overlaps that are less than 1
overlaps = overlaps.reduce((prev, current) => {
if (current.overlap == 1) {return prev.concat(current)} else {return prev};
}, []);
if (overlaps.length == 1) { return overlaps[0].side; }; // only one left;
return overlaps[overlaps.reduce((prev, current, index, array) => {
if (current.spacing[current.side] >= array[prev].spacing[prev.side]) {return index} else { return prev} ;
}, 0)].side;
}
/**
* @function getOrientation
* @desc Selects the appropriate orientation by deconflicting [auto-positioning]{@link Tip#autoPosition},
* [prefererd-orientation]{@link Tip#preferredOrientation, and [orientation]{@link Tip#orientation}
* settings in the Tip{} class object.
* @param {string} targetElement The DOM element that is associated with the tooltip.
* @param {Tip} target The [Tip]{@link Tip} class object containing all of the current tooltip's options and content.
* @returns {string} One of ['left' | 'right' | 'top' | 'bottom'].
* @since v2.1.2
*/
export function getOrientation (targetElement, target) {
if (target.orientation() != undefined) return target.orientation();
if (target.autoPosition()) return optimumOrientation (targetElement, target);
if (target.preferredOrientation() != 'none') return target.preferredOrientation();
return 'right';
}