{ "version": 3, "sources": ["../tooltip.js"], "sourcesContent": [ "'use strict'\n\nmodule.exports = {\n /**\n * Setup global options\n * These can and will be overwriteen if a config object is passed into this.init()\n */\n options: {\n html: false,\n placement: 'top',\n container: 'body',\n scrollContainer: window,\n template: '
',\n removalDelay: 200,\n tooltipOffset: 10,\n windowPadding: {\n top: 10,\n right: 10,\n bottom: 10,\n left: 10,\n },\n },\n\n /**\n * initializeTooltips - Initialize function to bind events and set any global data\n * @example\n * tooltips.init()\n * @param {object} (config) - Congifuration object that is used to overwrite the defaults in this.options\n * @return {void}\n */\n init: function initializeTooltips(config) {\n var self = this\n\n // Check to see if we should use document.body or document.documentElement\n document.documentElement.scrollTop = 1\n this.documentElement =\n document.documentElement.scrollTop === 1\n ? document.documentElement\n : document.body\n\n // Test for browser support\n var div = document.createElement('div')\n\n div.innerHTML =\n '' + title + '
'\n )\n\n tooltip.appendChild(tooltipTitle)\n }\n\n // If the supplied string should be interpreted as html, make an element for it...\n if ((this.options.html || html) && content) {\n if (this.isElement(content)) {\n tooltipContent = content\n } else if (this.getTagName(content)) {\n tooltipContent = this.createDOMElement(content)\n } else {\n tooltipContent = this.createDOMElement(\n '' + content + '
'\n )\n }\n // ...or create a default p instead\n } else {\n tooltipContent = this.createDOMElement(\n '' + content + '
'\n )\n }\n\n tooltip.appendChild(tooltipContent)\n\n // If the a container was supplied and it's not also the body element, store that element\n if (container && container !== this.options.container) {\n if (typeof container === 'string') {\n this.currentContainer = document.querySelector(container)\n }\n // If they initialized tooltips and set a different global container, store that element\n } else {\n this.currentContainer = document.querySelector(\n this.options.container\n )\n }\n\n // If a scrollContainer was supplied and it's also not the window element, store that element\n if (\n scrollContainer &&\n scrollContainer !== this.options.scrollContainer\n ) {\n if (typeof scrollContainer === 'string') {\n this.currentScrollContainer =\n document.querySelector(scrollContainer)\n }\n // If they initialized tooltips and incase they set a different global container, store that element\n } else {\n this.currentScrollContainer = this.options.scrollContainer\n }\n\n // If autoClose is set to false, add an attribute for the event handler to look for\n if (\n options.autoClose === false ||\n trigger.getAttribute('data-tooltip-autoclose') === 'false'\n ) {\n tooltip.setAttribute('data-autoclose', 'false')\n }\n\n if (preExistingTooltip) {\n this.currentTooltip = preExistingTooltip.parentNode.insertBefore(\n tooltip,\n preExistingTooltip\n )\n } else {\n this.currentTooltip = this.currentContainer.appendChild(tooltip)\n }\n\n this.currentTrigger = trigger\n\n // Position the tooltip on the page\n this.positionTooltip(\n this.currentTooltip,\n this.currentTrigger,\n placement\n )\n\n // If a tooltip is open and the user scrolls, isotip needs to keep up with the trigger\n if (this.currentScrollContainer !== window) {\n this.addEventListener(\n this.currentScrollContainer,\n 'scroll',\n this.windowChangeHandler\n )\n }\n\n return this.currentTooltip\n },\n\n /**\n * closeTooltip - Main close function to close a specific tooltip\n * @example\n * tooltip.close( document.body.querySelector( '.tooltip' ))\n * @param {string|element} tooltip - The tooltip that needs to be closed\n * @return {void}\n */\n close: function closeTooltip(tooltip) {\n // We need a DOM element, so make it one if it isn't already\n if (typeof tooltip === 'string') {\n tooltip = document.body.querySelector(tooltip)\n }\n\n if (this.currentScrollContainer !== window) {\n this.removeEventListener(\n this.currentScrollContainer,\n 'scroll',\n this.windowChangeHandler\n )\n }\n\n this.removeClass(tooltip, 'visible')\n\n this.currentTooltip = null\n this.currentTrigger = null\n this.currentScrollContainer = null\n\n // We should assume that there will be some sort of tooltip animation with CSS or JS\n // So we can only remove the element after a certain period of time\n window.setTimeout(function removeElementFromDOM() {\n if (\n tooltip &&\n tooltip instanceof window.Element &&\n tooltip.parentNode\n ) {\n tooltip.parentNode.removeChild(tooltip)\n } else {\n tooltip = document.body.querySelector('.tooltip')\n\n if (tooltip && tooltip.parentNode) {\n tooltip.parentNode.removeChild(tooltip)\n }\n }\n }, this.options.removalDelay)\n },\n\n /**\n * positionTooltip - Logic for positioning the tooltip on the page\n * @example\n * this.positionTooltip( this.currentTooltip, this.currentTrigger, 'top' )\n * @param {string|element} tooltip - The tooltip that needs to be positioned\n * @param {string|element} trigger - The element that triggered the tooltip\n * @param {string} (placement) - The position that the tooltip needs to be placed in in relation to the trigger\n * @return {element} - Returns the tooltip that was positioned\n * @api private\n */\n positionTooltip: function positionTooltip(tooltip, trigger, placement) {\n if (typeof tooltip === 'string') {\n tooltip = document.body.querySelector(tooltip)\n }\n\n if (typeof trigger === 'string') {\n trigger = document.body.querySelector(trigger)\n }\n\n // Since we support this being done on scroll, we need a way to get the placement if it isn't specified\n if (!placement) {\n placement =\n trigger.getAttribute('data-tooltip-placement') ||\n this.options.placement\n }\n\n var self = this\n var tooltipAccent = tooltip.querySelector('.tooltip-accent')\n var triggerPosition = trigger.getBoundingClientRect()\n var triggerWidth = Math.floor(triggerPosition.width)\n var triggerHeight = Math.floor(triggerPosition.height)\n var triggerX = triggerPosition.left\n var triggerY = triggerPosition.top\n var windowTop = this.options.windowPadding.top\n var windowRight =\n (window.innerWidth ||\n document.documentElement.clientWidth ||\n document.body.clientWidth) - this.options.windowPadding.right\n var windowBottom =\n (window.innerHeight ||\n document.documentElement.clientHeight ||\n document.body.clientHeight) - this.options.windowPadding.bottom\n var windowLeft = this.options.windowPadding.left\n var containerTop\n var containerRight\n var containerBottom\n var containerLeft\n var tooltipX\n var tooltipY\n var tooltipWidth\n var tooltipHeight\n var tooltipRight\n var tooltipBottom\n var tooltipAccentWidth\n var tooltipAccentHeight\n\n if (this.currentScrollContainer.getBoundingClientRect) {\n var scrollContainerPosition =\n this.currentScrollContainer.getBoundingClientRect()\n\n if (scrollContainerPosition.top >= 0) {\n containerTop =\n scrollContainerPosition.top + this.options.windowPadding.top\n }\n\n if (scrollContainerPosition.right <= windowRight) {\n containerRight =\n scrollContainerPosition.right -\n this.options.windowPadding.right\n }\n\n if (scrollContainerPosition.bottom <= windowBottom) {\n containerBottom =\n scrollContainerPosition.bottom -\n this.options.windowPadding.bottom\n }\n\n if (scrollContainerPosition.left >= windowLeft) {\n containerLeft =\n scrollContainerPosition.left +\n this.options.windowPadding.left\n }\n }\n\n /**\n * We sometimes need to re-position the tooltip (I.E. switch from top to bottom)\n * Which is why these are separate functions\n */\n function positionTop() {\n tooltipX = triggerX - tooltipWidth / 2 + triggerWidth / 2\n tooltipY = triggerY - tooltipHeight - self.options.tooltipOffset\n tooltipRight = tooltipX + tooltipWidth\n\n self.addClass(tooltip, 'tooltip-top')\n\n if (!tooltipAccentWidth) {\n tooltipAccentWidth = parseInt(tooltipAccent.offsetWidth, 10)\n }\n\n if (!tooltipAccentHeight) {\n tooltipAccentHeight = parseInt(tooltipAccent.offsetHeight, 10)\n }\n\n if (triggerY + triggerHeight <= containerTop) {\n if (self.hasClass(tooltip, 'visible')) {\n self.removeClass(tooltip, 'visible')\n }\n }\n\n // If the tooltip extends beyond the right edge of the window...\n if (tooltipRight > windowRight || tooltipRight > containerRight) {\n tooltip.style.top = 'auto'\n tooltip.style.bottom =\n windowBottom +\n self.options.windowPadding.bottom -\n triggerY +\n self.options.tooltipOffset +\n 'px'\n tooltip.style.right = self.options.windowPadding.right + 'px'\n tooltipAccent.style.left = 'auto'\n tooltipAccent.style.right =\n triggerWidth / 2 -\n tooltipAccentWidth / 2 +\n (windowRight - triggerX - triggerWidth) +\n 'px'\n // ...or if the tooltip extends beyond the top of the window...\n } else if (tooltipY < windowTop || tooltipY < containerTop) {\n self.removeClass(tooltip, 'tooltip-top')\n\n return positionBottom()\n // ...or if the tooltip extends beyond the left edge of the window...\n } else if (tooltipX < windowLeft || tooltipX < containerLeft) {\n tooltip.style.top = 'auto'\n tooltip.style.bottom =\n windowBottom +\n self.options.windowPadding.bottom -\n triggerY +\n self.options.tooltipOffset +\n 'px'\n tooltip.style.left = self.options.windowPadding.left + 'px'\n tooltipAccent.style.right = 'auto'\n tooltipAccent.style.left =\n triggerWidth / 2 -\n tooltipAccentWidth / 2 +\n (triggerX - windowLeft) +\n 'px'\n // ...or it fits inside the window\n } else {\n tooltip.style.top = 'auto'\n tooltip.style.bottom =\n windowBottom +\n self.options.windowPadding.bottom -\n triggerY +\n self.options.tooltipOffset +\n 'px'\n tooltip.style.left = tooltipX + 'px'\n tooltipAccent.style.top = ''\n tooltipAccent.style.bottom = ''\n tooltipAccent.style.right = ''\n tooltipAccent.style.left =\n tooltipWidth / 2 - tooltipAccentWidth / 2 + 'px'\n }\n }\n\n function positionRight() {\n tooltipX = triggerX + triggerWidth + self.options.tooltipOffset\n tooltipY = triggerY - tooltipHeight / 2 + triggerHeight / 2\n tooltipRight = tooltipX + tooltipWidth\n\n self.addClass(tooltip, 'tooltip-right')\n\n if (!tooltipAccentHeight) {\n tooltipAccentHeight = parseInt(tooltipAccent.offsetHeight, 10)\n }\n\n // If the tooltip extends beyond the right edge of the screen...\n if (tooltipRight > windowRight || tooltipRight > containerRight) {\n self.removeClass(tooltip, 'tooltip-right')\n\n return positionTop()\n // ...or if it fits to the right of the trigger element\n } else {\n tooltip.style.top = tooltipY + 'px'\n tooltip.style.left = tooltipX + 'px'\n tooltipAccent.style.right = ''\n tooltipAccent.style.bottom = ''\n tooltipAccent.style.left = ''\n tooltipAccent.style.top =\n tooltipHeight / 2 - tooltipAccentHeight / 2 + 'px'\n }\n }\n\n function positionBottom() {\n tooltipX = triggerX - tooltipWidth / 2 + triggerWidth / 2\n tooltipY = triggerY + triggerHeight + self.options.tooltipOffset\n tooltipRight = tooltipX + tooltipWidth\n tooltipBottom = tooltipY + tooltipHeight\n\n self.addClass(tooltip, 'tooltip-bottom')\n\n if (!tooltipAccentWidth) {\n tooltipAccentWidth = parseInt(tooltipAccent.offsetWidth, 10)\n }\n\n if (triggerY >= containerBottom) {\n if (self.hasClass(tooltip, 'visible')) {\n self.removeClass(tooltip, 'visible')\n }\n }\n\n // If the tooltip extends beyond the right edge of the window...\n if (tooltipRight > windowRight || tooltipRight > containerRight) {\n tooltip.style.top = tooltipY + 'px'\n tooltip.style.right = self.options.windowPadding.right + 'px'\n tooltipAccent.style.left = 'auto'\n tooltipAccent.style.right =\n triggerWidth / 2 -\n tooltipAccentWidth / 2 +\n (windowRight - triggerX - triggerWidth) +\n 'px'\n // ...or if the tooltip extends beyond the top of the window...\n } else if (\n tooltipBottom > windowBottom ||\n tooltipBottom > containerBottom\n ) {\n self.removeClass(tooltip, 'tooltip-bottom')\n\n return positionTop()\n // ...or if the tooltip extends beyond the left edge of the window...\n } else if (tooltipX < windowLeft || tooltipX < windowLeft) {\n tooltip.style.top = tooltipY + 'px'\n tooltip.style.left = self.options.windowPadding.left + 'px'\n tooltipAccent.style.right = 'auto'\n tooltipAccent.style.left =\n triggerWidth / 2 -\n tooltipAccentWidth / 2 +\n (triggerX - windowLeft) +\n 'px'\n // ...or it fits inside the window\n } else {\n tooltip.style.top = tooltipY + 'px'\n tooltip.style.bottom = 'auto'\n tooltip.style.left = tooltipX + 'px'\n tooltipAccent.style.top = ''\n tooltipAccent.style.bottom = ''\n tooltipAccent.style.right = ''\n tooltipAccent.style.left =\n tooltipWidth / 2 - tooltipAccentWidth / 2 + 'px'\n }\n }\n\n function positionLeft() {\n tooltipX = triggerX - tooltipWidth - self.options.tooltipOffset\n tooltipY = triggerY - tooltipHeight / 2 + triggerHeight / 2\n\n self.addClass(tooltip, 'tooltip-left')\n\n if (!tooltipAccentHeight) {\n tooltipAccentHeight = parseInt(tooltipAccent.offsetHeight, 10)\n }\n\n // If the tooltip extends beyond the right edge of the screen...\n if (tooltipX < windowLeft || tooltipX < containerLeft) {\n self.removeClass(tooltip, 'tooltip-left')\n\n return positionTop()\n // ...or if it fits to the left of the trigger element\n } else {\n tooltip.style.top = tooltipY + 'px'\n tooltip.style.left = tooltipX + 'px'\n tooltipAccent.style.right = ''\n tooltipAccent.style.bottom = ''\n tooltipAccent.style.left = ''\n tooltipAccent.style.top =\n tooltipHeight / 2 - tooltipAccentHeight / 2 + 'px'\n }\n }\n\n tooltip.style.position = 'fixed'\n\n tooltipWidth = parseInt(tooltip.offsetWidth, 10)\n tooltipHeight = parseInt(tooltip.offsetHeight, 10)\n tooltipAccentWidth = parseInt(tooltipAccent.offsetWidth, 10)\n tooltipAccentHeight = parseInt(tooltipAccent.offsetHeight, 10)\n\n // clear any classes set before hand\n self.removeClass(tooltip, 'tooltip-top')\n self.removeClass(tooltip, 'tooltip-right')\n self.removeClass(tooltip, 'tooltip-bottom')\n self.removeClass(tooltip, 'tooltip-left')\n\n // position the tooltip\n if (placement === 'top') {\n positionTop()\n } else if (placement === 'right') {\n positionRight()\n } else if (placement === 'bottom') {\n positionBottom()\n } else if (placement === 'left') {\n positionLeft()\n }\n\n // try and give the tooltip enough time to position itself\n if (\n !self.hasClass(tooltip, 'visible') &&\n (containerTop === undefined ||\n (triggerY + triggerHeight > containerTop &&\n triggerY < containerBottom))\n ) {\n window.setTimeout(function () {\n self.addClass(tooltip, 'visible')\n }, 50)\n } else if (\n triggerY + triggerHeight <= containerTop ||\n triggerY >= containerBottom\n ) {\n self.removeClass(tooltip, 'visible')\n }\n\n return tooltip\n },\n\n /**\n * addEventListener - Small function to add an event listener. Should be compatible with IE8+\n * @example\n * this.addEventListener( document.body, 'click', this.open( this.currentTooltip ))\n * @param {element} el - The element node that needs to have the event listener added\n * @param {string} eventName - The event name (sans the \"on\")\n * @param {function} handler - The function to be run when the event is triggered\n * @return {element} - The element that had an event bound\n * @api private\n */\n addEventListener: function addEventListener(\n el,\n eventName,\n handler,\n useCapture\n ) {\n if (!useCapture) {\n useCapture = false\n }\n\n if (el.addEventListener) {\n el.addEventListener(eventName, handler, useCapture)\n\n return el\n } else {\n if (eventName === 'focus') {\n eventName = 'focusin'\n }\n\n el.attachEvent('on' + eventName, function () {\n handler.call(el)\n })\n\n return el\n }\n },\n\n /**\n * removeEventListener - Small function to remove and event listener. Should be compatible with IE8+\n * @example\n * this.removeEventListener( document.body, 'click', this.open( this.currentTooltip ))\n * @param {element} el - The element node that needs to have the event listener removed\n * @param {string} eventName - The event name (sans the \"on\")\n * @param {function} handler - The function that was to be run when the event is triggered\n * @return {element} - The element that had an event removed\n * @api private\n */\n removeEventListener: function removeEventListener(\n el,\n eventName,\n handler,\n useCapture\n ) {\n if (!useCapture) {\n useCapture = false\n }\n\n if (!el) {\n return\n }\n\n if (el.removeEventListener) {\n el.removeEventListener(eventName, handler, useCapture)\n } else {\n if (eventName === 'focus') {\n eventName = 'focusin'\n }\n\n el.detachEvent('on' + eventName, function () {\n handler.call(el)\n })\n }\n\n return el\n },\n\n /**\n * hasClass - Small function to see if an element has a specific class. Should be compatible with IE8+\n * @example\n * this.hasClass( this.currentTooltip, 'visible' )\n * @param {element} el - The element to check the class existence on\n * @param {string} className - The class to check for\n * @return {boolean} - True or false depending on if the element has the class\n * @api private\n */\n hasClass: function hasClass(el, className) {\n if (el.classList) {\n return el.classList.contains(className)\n } else {\n return new RegExp('(^| )' + className + '( |$)', 'gi').test(\n el.className\n )\n }\n },\n\n /**\n * addClass - Small function to add a class to an element. Should be compatible with IE8+\n * @example\n * this.addClass( this.currentTooltip, 'visible' )\n * @param {element} el - The element to add the class to\n * @param {string} className - The class name to add to the element\n * @return {element} - The element that had the class added to it\n * @api private\n */\n addClass: function addClass(el, className) {\n if (el.classList) {\n el.classList.add(className)\n } else {\n el.className += ' ' + className\n }\n\n return el\n },\n\n /**\n * removeClass - Small function to remove a class from an element. Should be compatible with IE8+\n * @example\n * this.removeClass( this.currentTooltip, 'visible' )\n * @param {element} el - The element to remove the class from\n * @param {string} className - The class name to remove from the element\n * @return {element} - The element that had the class removed from it\n * @api private\n */\n removeClass: function removeClass(el, className) {\n if (el) {\n if (el.classList) {\n el.classList.remove(className)\n } else {\n el.className = el.className.replace(\n new RegExp(\n '(^|\\\\b)' + className.split(' ').join('|') + '(\\\\b|$)',\n 'gi'\n ),\n ' '\n )\n }\n }\n\n return el\n },\n\n /**\n * setInnerText - Small function to set the inner text of an element. Should be compatible with IE8+\n * @example\n * this.setInnerText( this.currentTooltip, 'Hello world' )\n * @param {element} el - The element to have the text inserted into\n * @param {string} text - The text to insert into the element\n * @return {element} - The element with the new inner text\n * @api private\n */\n setInnerText: function setInnerText(el, text) {\n if (el.textContent !== undefined) {\n el.textcontent = text\n } else {\n el.innerText = text\n }\n\n return el\n },\n\n /**\n * getTagName - Small function to get the tag name of an html string.\n * @example\n * this.getTagName( '' )\n * @param {string} html - The string of html to check for the tag\n * @return {object} - The object containing the tag of the root element in the html string as well as some other basic info\n * @api private\n */\n getTagName: function getTagName(html) {\n return /<([\\w:]+)/.exec(html)\n },\n\n /**\n * isElement - Small function to determine if object is a DOM element.\n * @example\n * this.isElement( '' ); # false\n * @example\n * this.isElement( this.createElement('div') ); # true\n * @param {string} obj - The object to check\n * @return {boolean} - Whether or not the object is a DOM element.\n * @api private\n */\n isElement: function getTagName(obj) {\n return !!(obj && obj.nodeType === 1)\n },\n\n /**\n * createDOMElement - Small function to transform an html string into an element\n * @example\n * this.createDOMElement( '