smart-tooltip
Version:
Intelligently show tooltip based on container position
502 lines (376 loc) • 17 kB
JavaScript
// module.exports.smartTooltip = smartTooltip;
export const smartTooltip = function smartTooltip() {
var btnArray = document.querySelectorAll(".smart-tooltip-container");
var classes = ["right", "right-t", "right-b", "left", "left-t", "left-b", "bottom", "bottom-r", "bottom-l", "top", "top-r", "top-l"];
let offSetValue = 5;
btnArray.forEach(element => {
var mouseLeftTimeOut = {
timeout : 1
};
if (myfunc){
console.log(element.classList);
if(element.classList.contains("clicktoshow")){
let tooltip = element.querySelector(".smart-tooltip");
tooltip.classList.add("stick");
element.addEventListener("click", (event)=>{
myfunc(event,mouseLeftTimeOut)
});
}else{
element.addEventListener("mouseover", (event)=>{
myfunc(event,mouseLeftTimeOut)
});
}
}
element.addEventListener("mouseleave", (event)=>{
mouseLeft(event,mouseLeftTimeOut)
});
let tooltip = element.querySelector(".smart-tooltip");
//Assign tabIndex to allow element to be focusable
tooltip.tabIndex = 1;
tooltip.addEventListener("blur", (event) => {
toolTipBlured(event.target);
});
tooltip.addEventListener("mouseleave", (event) => {
tooltipMouseLeft(event.target);
});
tooltip.addEventListener("mouseenter", (event) => {
tooltipMouseEnter(event.target);
});
});
function toolTipBlured(tooltip) {
if (removeOverlayClass(tooltip)) {
let tooltipRect = tooltip.getBoundingClientRect();
//Reset Inline Styles
tooltip.style.position = "";
tooltip.style.top = "";
tooltip.style.left = "";
tooltip.style.bottom = "";
tooltip.style.right = "";
tooltip.classList.add("overlay");
}
tooltip.style.visibility = "collapse"
tooltip.style.opacity = "0";
tooltip.style.display = "none";
}
function myfunc(event,mouseLeftTimeOut) {
//clear the timeout that was started by the mouse leave event
clearTimeout(mouseLeftTimeOut.timeout);
//console.log(mouseLeftTimeOut.timeout);
let windowWidth = window.innerWidth;
let windowHeight = window.innerHeight;
if (!event)
return;
let btn = event.target;
if (!btn) return;
let tooltip = btn.querySelector(".smart-tooltip");
if (!tooltip) return;
tooltip.style.visibility = "visible";
tooltip.style.opacity = "1";
tooltip.style.display = "block";
var tooltipclient = tooltip.getBoundingClientRect();
var btnclient = btn.getBoundingClientRect();
var spaces = {
left: btnclient.left > tooltipclient.width,
right: (windowWidth - btnclient.right) > tooltipclient.width,
top: btnclient.top > tooltipclient.height,
bottom: (windowHeight - btnclient.bottom) > tooltipclient.height,
getSpace: function (key) {
let stayClass = getStayClass(tooltip);
// debugger;
if (stayClass) {
if (stayClass == key)
return true;
else
return false;
}
return this[key];
}
}
removeTooltipClass(tooltip);
if (spaces.getSpace("left")) {
let topHalf = (btnclient.bottom - (btnclient.height / 2));
let bottomHalf = windowHeight - topHalf;
let halfTooltip = (tooltipclient.height / 2);
let isContainerBigger = btnclient.height >= tooltipclient.height;
if (isContainerBigger || ((topHalf > halfTooltip) && (bottomHalf > halfTooltip))) {
if (!manipulateOverlayIfPresent(btnclient, tooltip, "left")) {
tooltip.classList.add('left');
}
} else if (topHalf <= halfTooltip) {
if (!manipulateOverlayIfPresent(btnclient, tooltip, "left-b")) {
tooltip.classList.add('left-b');
}
} else if (bottomHalf <= halfTooltip) {
if (!manipulateOverlayIfPresent(btnclient, tooltip, "left-t")) {
tooltip.classList.add('left-t');
}
}
} else if (spaces.getSpace("right")) {
let topHalf = (btnclient.bottom - (btnclient.height / 2));
let bottomHalf = windowHeight - topHalf;
let halfTooltip = (tooltipclient.height / 2);
let isContainerBigger = btnclient.height >= tooltipclient.height;
if (isContainerBigger || ((topHalf > halfTooltip) && (bottomHalf > halfTooltip))) {
if (!manipulateOverlayIfPresent(btnclient, tooltip, "right")) {
tooltip.classList.add('right');
}
} else if (topHalf <= halfTooltip) {
if (!manipulateOverlayIfPresent(btnclient, tooltip, "right-b")) {
tooltip.classList.add('right-b');
}
} else if (bottomHalf <= halfTooltip) {
if (!manipulateOverlayIfPresent(btnclient, tooltip, "right-t")) {
tooltip.classList.add('right-t');
}
}
} else if (spaces.getSpace("top")) {
let leftHalf = (btnclient.right - (btnclient.width / 2));
let rightHalf = windowWidth - leftHalf;
let halfTooltip = (tooltipclient.width / 2);
let isContainerBigger = btnclient.width >= tooltipclient.width;
if (isContainerBigger || ((leftHalf > halfTooltip) && (rightHalf > halfTooltip))) {
if (!manipulateOverlayIfPresent(btnclient, tooltip, "top")) {
tooltip.classList.add('top');
}
} else if (leftHalf <= halfTooltip) {
if (!manipulateOverlayIfPresent(btnclient, tooltip, "top-r")) {
tooltip.classList.add('top-r');
}
} else if (rightHalf <= halfTooltip) {
if (!manipulateOverlayIfPresent(btnclient, tooltip, "top-l")) {
tooltip.classList.add('top-l');
}
}
} else if (spaces.getSpace("bottom")) {
let leftHalf = (btnclient.right - (btnclient.width / 2));
let rightHalf = windowWidth - leftHalf;
let halfTooltip = (tooltipclient.width / 2);
let isContainerBigger = btnclient.width >= tooltipclient.width;
if (isContainerBigger || ((leftHalf > halfTooltip) && (rightHalf > halfTooltip))) {
if (!manipulateOverlayIfPresent(btnclient, tooltip, "bottom")) {
tooltip.classList.add('bottom');
}
} else if (leftHalf <= halfTooltip) {
if (!manipulateOverlayIfPresent(btnclient, tooltip, "bottom-r")) {
tooltip.classList.add('bottom-r');
}
} else if (rightHalf <= halfTooltip) {
if (!manipulateOverlayIfPresent(btnclient, tooltip, "bottom-l")) {
tooltip.classList.add('bottom-l');
}
}
} else {
if (!manipulateOverlayIfPresent(btnclient, tooltip, "bottom")) {
tooltip.classList.add('bottom');
}
}
}
/**
* Remove all classes associated with the tooltips position
* @param {ElementRef} tooltip The tooltip element
*/
function removeTooltipClass(tooltip) {
let classList = tooltip.classList;
//search through a predefined classlist if tooltip has any on
//then remove it
classList.forEach(element => {
if (classes.includes(element)) {
classList.remove(element);
}
});
}
/**
* Gets the position the element is supposed to stay if it has a stay class
* eg. returns 'right' if stay-right class is present
* @param {HtmlElement} tooltip the tooltip element
*/
function getStayClass(tooltip) {
var classList = tooltip.classList;
let stayPosition;
classList.forEach(element => {
if (element.includes(`stay-`)) {
stayPosition = element.split("-")[1];
}
});
return stayPosition;
}
function mouseLeft(event,mouseLeftTimeOut) {
//console.log("Button Mouse Left");
if (!event)
return;
let btn = event.target;
if (!btn) return;
let tooltip = btn.querySelector(".smart-tooltip");
if (!tooltip) return;
//Make sure its not visible after mouse has left
var stickyTime = getStickyTime(tooltip);
if (!isNaN(stickyTime)) {
mouseLeftTimeOut.timeout = setTimeout(() => {
if (tooltip.classList.contains("hovered"))
return;
if (removeOverlayClass(tooltip)) {
let tooltipRect = tooltip.getBoundingClientRect();
//Reset Inline Styles
tooltip.style.position = "";
tooltip.style.top = "";
tooltip.style.left = "";
tooltip.style.bottom = "";
tooltip.style.right = "";
tooltip.classList.add("overlay");
}
tooltip.style.visibility = "collapse"
tooltip.style.opacity = "0";
tooltip.style.display = "none";
}, stickyTime);
//console.log(mouseLeftTimeOut);
} else {
tooltip.focus();
}
}
function removeOverlayClass(tooltip) {
let hasOverlay = false
if (tooltip.classList.contains("overlayed")) {
hasOverlay = true;
tooltip.classList.remove("overlayed");
}
return hasOverlay;
}
function removeOverlayMarkClass(tooltip) {
let hasOverlay = false
if (tooltip.classList.contains("overlay")) {
hasOverlay = true;
tooltip.classList.remove("overlay");
}
return hasOverlay;
}
/**
* Calculate the position of the tooltip based on the fixed position of the container
* @param {DOMClientRect} btnclient The Rectangle that defines the button element
* @param {string} tooltipPostion The position the tooltip should be displayed
*/
function getOverlayCoordinates(btnclient, tooltipclient, tooltipPostion) {
let windowWidth = window.innerWidth;
let windowHeight = window.innerHeight;
if (tooltipPostion.includes("right")) {
let overlayCoordinates = {
left: btnclient.right + offSetValue,
top: (btnclient.top + (btnclient.height / 2)) - (tooltipclient.height / 2),
props: ["left", "top"]
}
if (tooltipPostion == "right-t") {
overlayCoordinates.bottom = windowHeight - btnclient.bottom;
overlayCoordinates.props = ["left", "bottom"];
} else if (tooltipPostion == "right-b") {
overlayCoordinates.top = btnclient.top + offSetValue;
}
return overlayCoordinates;
} else if (tooltipPostion.includes("left")) {
//console.log(tooltipclient.height);
let overlayCoordinates = {
right: (windowWidth - btnclient.left) + offSetValue,
top: (btnclient.top + (btnclient.height / 2)) - (tooltipclient.height / 2),
props: ["right", "top"]
}
if (tooltipPostion == "left-t") {
overlayCoordinates.bottom = windowHeight - btnclient.bottom;
overlayCoordinates.props = ["right", "bottom"];
} else if (tooltipPostion == "left-b") {
overlayCoordinates.top = btnclient.top + offSetValue;
}
return overlayCoordinates;
} else if (tooltipPostion.includes("top")) {
let overlayCoordinates = {
left: (btnclient.right - (btnclient.width / 2)) - (tooltipclient.width / 2),
bottom: (windowHeight - btnclient.top) + offSetValue,
props: ["left", "bottom"]
}
if (tooltipPostion == "top-l") {
overlayCoordinates.right = (windowWidth - btnclient.right) + offSetValue;
overlayCoordinates.props = ["right", "bottom"]
} else if (tooltipPostion == "top-r") {
overlayCoordinates.left = btnclient.left + offSetValue;
}
return overlayCoordinates;
} else if (tooltipPostion.includes("bottom")) {
let overlayCoordinates = {
left: (btnclient.right - (btnclient.width / 2)) - (tooltipclient.width / 2),
top: btnclient.bottom + offSetValue,
props: ["left", "top"]
}
if (tooltipPostion == "bottom-l") {
overlayCoordinates.right = (windowWidth - btnclient.right) + offSetValue;
overlayCoordinates.props = ["right", "top"]
} else if (tooltipPostion == "bottom-r") {
overlayCoordinates.left = btnclient.left + offSetValue;
}
return overlayCoordinates;
}
}
/**
* Checks for overlay class and manipulate it
* Responsible for assigning the position if overlay class is detected on the element
* @param {any} tooltip the tooltip element
* @param {string} position the string that show which side the tooltip is
*/
function manipulateOverlayIfPresent(btnBClientRect, tooltip, tooltipPosition) {
//removeOverlayMarkClass checks for the presence of the overlay mark i.e
//overlay .. remove it and add the overlayed class which has the css declarations
//debugger;
if (removeOverlayMarkClass(tooltip)) {
// debugger;
//Get the overlay coordinates
let overlayCoordinates = getOverlayCoordinates(btnBClientRect, tooltip.getBoundingClientRect(), tooltipPosition);
//Get the set properties base the tooltip position based on the tooltipPosition
//Overlay styles to tooltip control
tooltip.style.position = "fixed";
for (let prop of overlayCoordinates.props) {
tooltip.style[prop] = overlayCoordinates[prop] + "px";
}
//Add the actual overlayed class which resets old styles
tooltip.classList.add("overlayed");
return true;
}
return false;
}
/**
* Get the sticky time on the tooltip or returns zero if none is found
* @param {HtmlElement} tooltip The tooltip element
*/
function getStickyTime(tooltip) {
try {
for (let item of tooltip.classList) {
if (item.includes("stick")) {
if (item.includes("-")) {
let numberstring = item.split("-")[1];
return +numberstring;
} else {
return Number.NaN;
}
}
}
} catch{
return 0;
}
//return zero if tooltip doesn't have a sticky class
return 0;
}
function tooltipMouseLeft(tooltip) {
if (tooltip.classList.contains('hovered')) {
if (!isNaN(getStickyTime(tooltip))) {
tooltip.classList.add("stayonhover");
tooltip.classList.remove('hovered');
//console.log("blured");
//tooltip.blur();
}
}
}
function tooltipMouseEnter(tooltip) {
if (tooltip.classList.contains('stayonhover')) {
if (!isNaN(getStickyTime(tooltip))) {
tooltip.classList.remove("stayonhover");
tooltip.classList.add('hovered');
//tooltip.focus();
}
}
}
}