UNPKG

tippy.js

Version:

Highly customizable tooltip and popover library

1,289 lines (1,079 loc) 69.7 kB
/**! * tippy.js v4.3.5 * (c) 2017-2019 atomiks * MIT License */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('popper.js')) : typeof define === 'function' && define.amd ? define(['popper.js'], factory) : (global = global || self, global.tippy = factory(global.Popper)); }(this, function (Popper) { 'use strict'; Popper = Popper && Popper.hasOwnProperty('default') ? Popper['default'] : Popper; var css = ".tippy-iOS{cursor:pointer!important;-webkit-tap-highlight-color:transparent}.tippy-popper{transition-timing-function:cubic-bezier(.165,.84,.44,1);max-width:calc(100% - 8px);pointer-events:none;outline:0}.tippy-popper[x-placement^=top] .tippy-backdrop{border-radius:40% 40% 0 0}.tippy-popper[x-placement^=top] .tippy-roundarrow{bottom:-7px;bottom:-6.5px;-webkit-transform-origin:50% 0;transform-origin:50% 0;margin:0 3px}.tippy-popper[x-placement^=top] .tippy-roundarrow svg{position:absolute;left:0;-webkit-transform:rotate(180deg);transform:rotate(180deg)}.tippy-popper[x-placement^=top] .tippy-arrow{border-top:8px solid #333;border-right:8px solid transparent;border-left:8px solid transparent;bottom:-7px;margin:0 3px;-webkit-transform-origin:50% 0;transform-origin:50% 0}.tippy-popper[x-placement^=top] .tippy-backdrop{-webkit-transform-origin:0 25%;transform-origin:0 25%}.tippy-popper[x-placement^=top] .tippy-backdrop[data-state=visible]{-webkit-transform:scale(1) translate(-50%,-55%);transform:scale(1) translate(-50%,-55%)}.tippy-popper[x-placement^=top] .tippy-backdrop[data-state=hidden]{-webkit-transform:scale(.2) translate(-50%,-45%);transform:scale(.2) translate(-50%,-45%);opacity:0}.tippy-popper[x-placement^=top] [data-animation=shift-toward][data-state=visible]{-webkit-transform:translateY(-10px);transform:translateY(-10px)}.tippy-popper[x-placement^=top] [data-animation=shift-toward][data-state=hidden]{opacity:0;-webkit-transform:translateY(-20px);transform:translateY(-20px)}.tippy-popper[x-placement^=top] [data-animation=perspective]{-webkit-transform-origin:bottom;transform-origin:bottom}.tippy-popper[x-placement^=top] [data-animation=perspective][data-state=visible]{-webkit-transform:perspective(700px) translateY(-10px);transform:perspective(700px) translateY(-10px)}.tippy-popper[x-placement^=top] [data-animation=perspective][data-state=hidden]{opacity:0;-webkit-transform:perspective(700px) rotateX(60deg);transform:perspective(700px) rotateX(60deg)}.tippy-popper[x-placement^=top] [data-animation=fade][data-state=visible]{-webkit-transform:translateY(-10px);transform:translateY(-10px)}.tippy-popper[x-placement^=top] [data-animation=fade][data-state=hidden]{opacity:0;-webkit-transform:translateY(-10px);transform:translateY(-10px)}.tippy-popper[x-placement^=top] [data-animation=shift-away][data-state=visible]{-webkit-transform:translateY(-10px);transform:translateY(-10px)}.tippy-popper[x-placement^=top] [data-animation=shift-away][data-state=hidden]{opacity:0}.tippy-popper[x-placement^=top] [data-animation=scale]{-webkit-transform-origin:bottom;transform-origin:bottom}.tippy-popper[x-placement^=top] [data-animation=scale][data-state=visible]{-webkit-transform:translateY(-10px);transform:translateY(-10px)}.tippy-popper[x-placement^=top] [data-animation=scale][data-state=hidden]{opacity:0;-webkit-transform:translateY(-10px) scale(.5);transform:translateY(-10px) scale(.5)}.tippy-popper[x-placement^=bottom] .tippy-backdrop{border-radius:0 0 30% 30%}.tippy-popper[x-placement^=bottom] .tippy-roundarrow{top:-7px;-webkit-transform-origin:50% 100%;transform-origin:50% 100%;margin:0 3px}.tippy-popper[x-placement^=bottom] .tippy-roundarrow svg{position:absolute;left:0}.tippy-popper[x-placement^=bottom] .tippy-arrow{border-bottom:8px solid #333;border-right:8px solid transparent;border-left:8px solid transparent;top:-7px;margin:0 3px;-webkit-transform-origin:50% 100%;transform-origin:50% 100%}.tippy-popper[x-placement^=bottom] .tippy-backdrop{-webkit-transform-origin:0 -50%;transform-origin:0 -50%}.tippy-popper[x-placement^=bottom] .tippy-backdrop[data-state=visible]{-webkit-transform:scale(1) translate(-50%,-45%);transform:scale(1) translate(-50%,-45%)}.tippy-popper[x-placement^=bottom] .tippy-backdrop[data-state=hidden]{-webkit-transform:scale(.2) translate(-50%);transform:scale(.2) translate(-50%);opacity:0}.tippy-popper[x-placement^=bottom] [data-animation=shift-toward][data-state=visible]{-webkit-transform:translateY(10px);transform:translateY(10px)}.tippy-popper[x-placement^=bottom] [data-animation=shift-toward][data-state=hidden]{opacity:0;-webkit-transform:translateY(20px);transform:translateY(20px)}.tippy-popper[x-placement^=bottom] [data-animation=perspective]{-webkit-transform-origin:top;transform-origin:top}.tippy-popper[x-placement^=bottom] [data-animation=perspective][data-state=visible]{-webkit-transform:perspective(700px) translateY(10px);transform:perspective(700px) translateY(10px)}.tippy-popper[x-placement^=bottom] [data-animation=perspective][data-state=hidden]{opacity:0;-webkit-transform:perspective(700px) rotateX(-60deg);transform:perspective(700px) rotateX(-60deg)}.tippy-popper[x-placement^=bottom] [data-animation=fade][data-state=visible]{-webkit-transform:translateY(10px);transform:translateY(10px)}.tippy-popper[x-placement^=bottom] [data-animation=fade][data-state=hidden]{opacity:0;-webkit-transform:translateY(10px);transform:translateY(10px)}.tippy-popper[x-placement^=bottom] [data-animation=shift-away][data-state=visible]{-webkit-transform:translateY(10px);transform:translateY(10px)}.tippy-popper[x-placement^=bottom] [data-animation=shift-away][data-state=hidden]{opacity:0}.tippy-popper[x-placement^=bottom] [data-animation=scale]{-webkit-transform-origin:top;transform-origin:top}.tippy-popper[x-placement^=bottom] [data-animation=scale][data-state=visible]{-webkit-transform:translateY(10px);transform:translateY(10px)}.tippy-popper[x-placement^=bottom] [data-animation=scale][data-state=hidden]{opacity:0;-webkit-transform:translateY(10px) scale(.5);transform:translateY(10px) scale(.5)}.tippy-popper[x-placement^=left] .tippy-backdrop{border-radius:50% 0 0 50%}.tippy-popper[x-placement^=left] .tippy-roundarrow{right:-12px;-webkit-transform-origin:33.33333333% 50%;transform-origin:33.33333333% 50%;margin:3px 0}.tippy-popper[x-placement^=left] .tippy-roundarrow svg{position:absolute;left:0;-webkit-transform:rotate(90deg);transform:rotate(90deg)}.tippy-popper[x-placement^=left] .tippy-arrow{border-left:8px solid #333;border-top:8px solid transparent;border-bottom:8px solid transparent;right:-7px;margin:3px 0;-webkit-transform-origin:0 50%;transform-origin:0 50%}.tippy-popper[x-placement^=left] .tippy-backdrop{-webkit-transform-origin:50% 0;transform-origin:50% 0}.tippy-popper[x-placement^=left] .tippy-backdrop[data-state=visible]{-webkit-transform:scale(1) translate(-50%,-50%);transform:scale(1) translate(-50%,-50%)}.tippy-popper[x-placement^=left] .tippy-backdrop[data-state=hidden]{-webkit-transform:scale(.2) translate(-75%,-50%);transform:scale(.2) translate(-75%,-50%);opacity:0}.tippy-popper[x-placement^=left] [data-animation=shift-toward][data-state=visible]{-webkit-transform:translateX(-10px);transform:translateX(-10px)}.tippy-popper[x-placement^=left] [data-animation=shift-toward][data-state=hidden]{opacity:0;-webkit-transform:translateX(-20px);transform:translateX(-20px)}.tippy-popper[x-placement^=left] [data-animation=perspective]{-webkit-transform-origin:right;transform-origin:right}.tippy-popper[x-placement^=left] [data-animation=perspective][data-state=visible]{-webkit-transform:perspective(700px) translateX(-10px);transform:perspective(700px) translateX(-10px)}.tippy-popper[x-placement^=left] [data-animation=perspective][data-state=hidden]{opacity:0;-webkit-transform:perspective(700px) rotateY(-60deg);transform:perspective(700px) rotateY(-60deg)}.tippy-popper[x-placement^=left] [data-animation=fade][data-state=visible]{-webkit-transform:translateX(-10px);transform:translateX(-10px)}.tippy-popper[x-placement^=left] [data-animation=fade][data-state=hidden]{opacity:0;-webkit-transform:translateX(-10px);transform:translateX(-10px)}.tippy-popper[x-placement^=left] [data-animation=shift-away][data-state=visible]{-webkit-transform:translateX(-10px);transform:translateX(-10px)}.tippy-popper[x-placement^=left] [data-animation=shift-away][data-state=hidden]{opacity:0}.tippy-popper[x-placement^=left] [data-animation=scale]{-webkit-transform-origin:right;transform-origin:right}.tippy-popper[x-placement^=left] [data-animation=scale][data-state=visible]{-webkit-transform:translateX(-10px);transform:translateX(-10px)}.tippy-popper[x-placement^=left] [data-animation=scale][data-state=hidden]{opacity:0;-webkit-transform:translateX(-10px) scale(.5);transform:translateX(-10px) scale(.5)}.tippy-popper[x-placement^=right] .tippy-backdrop{border-radius:0 50% 50% 0}.tippy-popper[x-placement^=right] .tippy-roundarrow{left:-12px;-webkit-transform-origin:66.66666666% 50%;transform-origin:66.66666666% 50%;margin:3px 0}.tippy-popper[x-placement^=right] .tippy-roundarrow svg{position:absolute;left:0;-webkit-transform:rotate(-90deg);transform:rotate(-90deg)}.tippy-popper[x-placement^=right] .tippy-arrow{border-right:8px solid #333;border-top:8px solid transparent;border-bottom:8px solid transparent;left:-7px;margin:3px 0;-webkit-transform-origin:100% 50%;transform-origin:100% 50%}.tippy-popper[x-placement^=right] .tippy-backdrop{-webkit-transform-origin:-50% 0;transform-origin:-50% 0}.tippy-popper[x-placement^=right] .tippy-backdrop[data-state=visible]{-webkit-transform:scale(1) translate(-50%,-50%);transform:scale(1) translate(-50%,-50%)}.tippy-popper[x-placement^=right] .tippy-backdrop[data-state=hidden]{-webkit-transform:scale(.2) translate(-25%,-50%);transform:scale(.2) translate(-25%,-50%);opacity:0}.tippy-popper[x-placement^=right] [data-animation=shift-toward][data-state=visible]{-webkit-transform:translateX(10px);transform:translateX(10px)}.tippy-popper[x-placement^=right] [data-animation=shift-toward][data-state=hidden]{opacity:0;-webkit-transform:translateX(20px);transform:translateX(20px)}.tippy-popper[x-placement^=right] [data-animation=perspective]{-webkit-transform-origin:left;transform-origin:left}.tippy-popper[x-placement^=right] [data-animation=perspective][data-state=visible]{-webkit-transform:perspective(700px) translateX(10px);transform:perspective(700px) translateX(10px)}.tippy-popper[x-placement^=right] [data-animation=perspective][data-state=hidden]{opacity:0;-webkit-transform:perspective(700px) rotateY(60deg);transform:perspective(700px) rotateY(60deg)}.tippy-popper[x-placement^=right] [data-animation=fade][data-state=visible]{-webkit-transform:translateX(10px);transform:translateX(10px)}.tippy-popper[x-placement^=right] [data-animation=fade][data-state=hidden]{opacity:0;-webkit-transform:translateX(10px);transform:translateX(10px)}.tippy-popper[x-placement^=right] [data-animation=shift-away][data-state=visible]{-webkit-transform:translateX(10px);transform:translateX(10px)}.tippy-popper[x-placement^=right] [data-animation=shift-away][data-state=hidden]{opacity:0}.tippy-popper[x-placement^=right] [data-animation=scale]{-webkit-transform-origin:left;transform-origin:left}.tippy-popper[x-placement^=right] [data-animation=scale][data-state=visible]{-webkit-transform:translateX(10px);transform:translateX(10px)}.tippy-popper[x-placement^=right] [data-animation=scale][data-state=hidden]{opacity:0;-webkit-transform:translateX(10px) scale(.5);transform:translateX(10px) scale(.5)}.tippy-tooltip{position:relative;color:#fff;border-radius:.25rem;font-size:.875rem;padding:.3125rem .5625rem;line-height:1.4;text-align:center;background-color:#333}.tippy-tooltip[data-size=small]{padding:.1875rem .375rem;font-size:.75rem}.tippy-tooltip[data-size=large]{padding:.375rem .75rem;font-size:1rem}.tippy-tooltip[data-animatefill]{overflow:hidden;background-color:initial}.tippy-tooltip[data-interactive],.tippy-tooltip[data-interactive] .tippy-roundarrow path{pointer-events:auto}.tippy-tooltip[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-tooltip[data-inertia][data-state=hidden]{transition-timing-function:ease}.tippy-arrow,.tippy-roundarrow{position:absolute;width:0;height:0}.tippy-roundarrow{width:18px;height:7px;fill:#333;pointer-events:none}.tippy-backdrop{position:absolute;background-color:#333;border-radius:50%;width:calc(110% + 2rem);left:50%;top:50%;z-index:-1;transition:all cubic-bezier(.46,.1,.52,.98);-webkit-backface-visibility:hidden;backface-visibility:hidden}.tippy-backdrop:after{content:\"\";float:left;padding-top:100%}.tippy-backdrop+.tippy-content{transition-property:opacity;will-change:opacity}.tippy-backdrop+.tippy-content[data-state=hidden]{opacity:0}"; function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } var version = "4.3.5"; var isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined'; var ua = isBrowser ? navigator.userAgent : ''; var isIE = /MSIE |Trident\//.test(ua); var isUCBrowser = /UCBrowser\//.test(ua); var isIOS = isBrowser && /iPhone|iPad|iPod/.test(navigator.platform) && !window.MSStream; var defaultProps = { a11y: true, allowHTML: true, animateFill: true, animation: 'shift-away', appendTo: function appendTo() { return document.body; }, aria: 'describedby', arrow: false, arrowType: 'sharp', boundary: 'scrollParent', content: '', delay: 0, distance: 10, duration: [325, 275], flip: true, flipBehavior: 'flip', flipOnUpdate: false, followCursor: false, hideOnClick: true, ignoreAttributes: false, inertia: false, interactive: false, interactiveBorder: 2, interactiveDebounce: 0, lazy: true, maxWidth: 350, multiple: false, offset: 0, onHidden: function onHidden() {}, onHide: function onHide() {}, onMount: function onMount() {}, onShow: function onShow() {}, onShown: function onShown() {}, onTrigger: function onTrigger() {}, placement: 'top', popperOptions: {}, role: 'tooltip', showOnInit: false, size: 'regular', sticky: false, target: '', theme: 'dark', touch: true, touchHold: false, trigger: 'mouseenter focus', triggerTarget: null, updateDuration: 0, wait: null, zIndex: 9999 /** * If the set() method encounters one of these, the popperInstance must be * recreated */ }; var POPPER_INSTANCE_DEPENDENCIES = ['arrow', 'arrowType', 'boundary', 'distance', 'flip', 'flipBehavior', 'flipOnUpdate', 'offset', 'placement', 'popperOptions']; var elementProto = isBrowser ? Element.prototype : {}; var matches = elementProto.matches || elementProto.matchesSelector || elementProto.webkitMatchesSelector || elementProto.mozMatchesSelector || elementProto.msMatchesSelector; /** * Ponyfill for Array.from - converts iterable values to an array */ function arrayFrom(value) { return [].slice.call(value); } /** * Ponyfill for Element.prototype.closest */ function closest(element, selector) { return closestCallback(element, function (el) { return matches.call(el, selector); }); } /** * Works like Element.prototype.closest, but uses a callback instead */ function closestCallback(element, callback) { while (element) { if (callback(element)) { return element; } element = element.parentElement; } return null; } // Passive event listener config var PASSIVE = { passive: true // Popper `preventOverflow` padding }; var PADDING = 4; // Popper attributes // In Popper v2 these will be `data-*` instead of `x-*` to adhere to HTML5 spec var PLACEMENT_ATTRIBUTE = 'x-placement'; var OUT_OF_BOUNDARIES_ATTRIBUTE = 'x-out-of-boundaries'; // Classes var IOS_CLASS = "tippy-iOS"; var ACTIVE_CLASS = "tippy-active"; var POPPER_CLASS = "tippy-popper"; var TOOLTIP_CLASS = "tippy-tooltip"; var CONTENT_CLASS = "tippy-content"; var BACKDROP_CLASS = "tippy-backdrop"; var ARROW_CLASS = "tippy-arrow"; var ROUND_ARROW_CLASS = "tippy-roundarrow"; // Selectors var POPPER_SELECTOR = ".".concat(POPPER_CLASS); var TOOLTIP_SELECTOR = ".".concat(TOOLTIP_CLASS); var CONTENT_SELECTOR = ".".concat(CONTENT_CLASS); var BACKDROP_SELECTOR = ".".concat(BACKDROP_CLASS); var ARROW_SELECTOR = ".".concat(ARROW_CLASS); var ROUND_ARROW_SELECTOR = ".".concat(ROUND_ARROW_CLASS); var isUsingTouch = false; function onDocumentTouch() { if (isUsingTouch) { return; } isUsingTouch = true; if (isIOS) { document.body.classList.add(IOS_CLASS); } if (window.performance) { document.addEventListener('mousemove', onDocumentMouseMove); } } var lastMouseMoveTime = 0; function onDocumentMouseMove() { var now = performance.now(); // Chrome 60+ is 1 mousemove per animation frame, use 20ms time difference if (now - lastMouseMoveTime < 20) { isUsingTouch = false; document.removeEventListener('mousemove', onDocumentMouseMove); if (!isIOS) { document.body.classList.remove(IOS_CLASS); } } lastMouseMoveTime = now; } function onWindowBlur() { var _document = document, activeElement = _document.activeElement; if (activeElement && activeElement.blur && activeElement._tippy) { activeElement.blur(); } } /** * Adds the needed global event listeners */ function bindGlobalEventListeners() { document.addEventListener('touchstart', onDocumentTouch, PASSIVE); window.addEventListener('blur', onWindowBlur); } var keys = Object.keys(defaultProps); /** * Returns an object of optional props from data-tippy-* attributes */ function getDataAttributeOptions(reference) { return keys.reduce(function (acc, key) { var valueAsString = (reference.getAttribute("data-tippy-".concat(key)) || '').trim(); if (!valueAsString) { return acc; } if (key === 'content') { acc[key] = valueAsString; } else { try { acc[key] = JSON.parse(valueAsString); } catch (e) { acc[key] = valueAsString; } } return acc; }, {}); } /** * Polyfills the virtual reference (plain object) with Element.prototype props * Mutating because DOM elements are mutated, adds `_tippy` property */ function polyfillElementPrototypeProperties(virtualReference) { var polyfills = { isVirtual: true, attributes: virtualReference.attributes || {}, contains: function contains() {}, setAttribute: function setAttribute(key, value) { virtualReference.attributes[key] = value; }, getAttribute: function getAttribute(key) { return virtualReference.attributes[key]; }, removeAttribute: function removeAttribute(key) { delete virtualReference.attributes[key]; }, hasAttribute: function hasAttribute(key) { return key in virtualReference.attributes; }, addEventListener: function addEventListener() {}, removeEventListener: function removeEventListener() {}, classList: { classNames: {}, add: function add(key) { virtualReference.classList.classNames[key] = true; }, remove: function remove(key) { delete virtualReference.classList.classNames[key]; }, contains: function contains(key) { return key in virtualReference.classList.classNames; } } }; for (var key in polyfills) { virtualReference[key] = polyfills[key]; } } /** * Determines if a value is a "bare" virtual element (before mutations done * by `polyfillElementPrototypeProperties()`). JSDOM elements show up as * [object Object], we can check if the value is "element-like" if it has * `addEventListener` */ function isBareVirtualElement(value) { return {}.toString.call(value) === '[object Object]' && !value.addEventListener; } /** * Determines if the value is a reference element */ function isReferenceElement(value) { return !!value._tippy && !matches.call(value, POPPER_SELECTOR); } /** * Safe .hasOwnProperty check, for prototype-less objects */ function hasOwnProperty(obj, key) { return {}.hasOwnProperty.call(obj, key); } /** * Returns an array of elements based on the value */ function getArrayOfElements(value) { if (isSingular(value)) { // TODO: VirtualReference is not compatible to type Element return [value]; } if (value instanceof NodeList) { return arrayFrom(value); } if (Array.isArray(value)) { return value; } try { return arrayFrom(document.querySelectorAll(value)); } catch (e) { return []; } } /** * Returns a value at a given index depending on if it's an array or number */ function getValue(value, index, defaultValue) { if (Array.isArray(value)) { var v = value[index]; return v == null ? defaultValue : v; } return value; } /** * Debounce utility. To avoid bloating bundle size, we're only passing 1 * argument here, a more generic function would pass all arguments. Only * `onMouseMove` uses this which takes the event object for now. */ function debounce(fn, ms) { // Avoid wrapping in `setTimeout` if ms is 0 anyway if (ms === 0) { return fn; } var timeout; return function (arg) { clearTimeout(timeout); timeout = setTimeout(function () { fn(arg); }, ms); }; } /** * Prevents errors from being thrown while accessing nested modifier objects * in `popperOptions` */ function getModifier(obj, key) { return obj && obj.modifiers && obj.modifiers[key]; } /** * Determines if an array or string includes a value */ function includes(a, b) { return a.indexOf(b) > -1; } /** * Determines if the value is a real element */ function isRealElement(value) { return value instanceof Element; } /** * Determines if the value is singular-like */ function isSingular(value) { return !!(value && hasOwnProperty(value, 'isVirtual')) || isRealElement(value); } /** * Firefox extensions don't allow setting .innerHTML directly, this will trick it */ function innerHTML() { return 'innerHTML'; } /** * Evaluates a function if one, or returns the value */ function invokeWithArgsOrReturn(value, args) { return typeof value === 'function' ? value.apply(null, args) : value; } /** * Sets a popperInstance `flip` modifier's enabled state */ function setFlipModifierEnabled(modifiers, value) { modifiers.filter(function (m) { return m.name === 'flip'; })[0].enabled = value; } /** * Determines if an element can receive focus * Always returns true for virtual objects */ function canReceiveFocus(element) { return isRealElement(element) ? matches.call(element, 'a[href],area[href],button,details,input,textarea,select,iframe,[tabindex]') && !element.hasAttribute('disabled') : true; } /** * Returns a new `div` element */ function div() { return document.createElement('div'); } /** * Applies a transition duration to a list of elements */ function setTransitionDuration(els, value) { els.forEach(function (el) { if (el) { el.style.transitionDuration = "".concat(value, "ms"); } }); } /** * Sets the visibility state to elements so they can begin to transition */ function setVisibilityState(els, state) { els.forEach(function (el) { if (el) { el.setAttribute('data-state', state); } }); } /** * Evaluates the props object by merging data attributes and * disabling conflicting options where necessary */ function evaluateProps(reference, props) { var out = _extends({}, props, { content: invokeWithArgsOrReturn(props.content, [reference]) }, props.ignoreAttributes ? {} : getDataAttributeOptions(reference)); if (out.arrow || isUCBrowser) { out.animateFill = false; } return out; } /** * Validates an object of options with the valid default props object */ function validateOptions(options, defaultProps) { Object.keys(options).forEach(function (option) { if (!hasOwnProperty(defaultProps, option)) { throw new Error("[tippy]: `".concat(option, "` is not a valid option")); } }); } /** * Sets the innerHTML of an element */ function setInnerHTML(element, html) { element[innerHTML()] = isRealElement(html) ? html[innerHTML()] : html; } /** * Sets the content of a tooltip */ function setContent(contentEl, props) { if (isRealElement(props.content)) { setInnerHTML(contentEl, ''); contentEl.appendChild(props.content); } else if (typeof props.content !== 'function') { var key = props.allowHTML ? 'innerHTML' : 'textContent'; contentEl[key] = props.content; } } /** * Returns the child elements of a popper element */ function getChildren(popper) { return { tooltip: popper.querySelector(TOOLTIP_SELECTOR), backdrop: popper.querySelector(BACKDROP_SELECTOR), content: popper.querySelector(CONTENT_SELECTOR), arrow: popper.querySelector(ARROW_SELECTOR) || popper.querySelector(ROUND_ARROW_SELECTOR) }; } /** * Adds `data-inertia` attribute */ function addInertia(tooltip) { tooltip.setAttribute('data-inertia', ''); } /** * Removes `data-inertia` attribute */ function removeInertia(tooltip) { tooltip.removeAttribute('data-inertia'); } /** * Creates an arrow element and returns it */ function createArrowElement(arrowType) { var arrow = div(); if (arrowType === 'round') { arrow.className = ROUND_ARROW_CLASS; setInnerHTML(arrow, '<svg viewBox="0 0 18 7" xmlns="http://www.w3.org/2000/svg"><path d="M0 7s2.021-.015 5.253-4.218C6.584 1.051 7.797.007 9 0c1.203-.007 2.416 1.035 3.761 2.782C16.012 7.005 18 7 18 7H0z"/></svg>'); } else { arrow.className = ARROW_CLASS; } return arrow; } /** * Creates a backdrop element and returns it */ function createBackdropElement() { var backdrop = div(); backdrop.className = BACKDROP_CLASS; backdrop.setAttribute('data-state', 'hidden'); return backdrop; } /** * Adds interactive-related attributes */ function addInteractive(popper, tooltip) { popper.setAttribute('tabindex', '-1'); tooltip.setAttribute('data-interactive', ''); } /** * Removes interactive-related attributes */ function removeInteractive(popper, tooltip) { popper.removeAttribute('tabindex'); tooltip.removeAttribute('data-interactive'); } /** * Add/remove transitionend listener from tooltip */ function updateTransitionEndListener(tooltip, action, listener) { // UC Browser hasn't adopted the `transitionend` event despite supporting // unprefixed transitions... var eventName = isUCBrowser && document.body.style.webkitTransition !== undefined ? 'webkitTransitionEnd' : 'transitionend'; tooltip[action + 'EventListener'](eventName, listener); } /** * Returns the popper's placement, ignoring shifting (top-start, etc) */ function getBasicPlacement(popper) { var fullPlacement = popper.getAttribute(PLACEMENT_ATTRIBUTE); return fullPlacement ? fullPlacement.split('-')[0] : ''; } /** * Triggers reflow */ function reflow(popper) { void popper.offsetHeight; } /** * Adds/removes theme from tooltip's classList */ function updateTheme(tooltip, action, theme) { theme.split(' ').forEach(function (themeName) { tooltip.classList[action](themeName + '-theme'); }); } /** * Constructs the popper element and returns it */ function createPopperElement(id, props) { var popper = div(); popper.className = POPPER_CLASS; popper.id = "tippy-".concat(id); popper.style.zIndex = '' + props.zIndex; popper.style.position = 'absolute'; popper.style.top = '0'; popper.style.left = '0'; if (props.role) { popper.setAttribute('role', props.role); } var tooltip = div(); tooltip.className = TOOLTIP_CLASS; tooltip.style.maxWidth = props.maxWidth + (typeof props.maxWidth === 'number' ? 'px' : ''); tooltip.setAttribute('data-size', props.size); tooltip.setAttribute('data-animation', props.animation); tooltip.setAttribute('data-state', 'hidden'); updateTheme(tooltip, 'add', props.theme); var content = div(); content.className = CONTENT_CLASS; content.setAttribute('data-state', 'hidden'); if (props.interactive) { addInteractive(popper, tooltip); } if (props.arrow) { tooltip.appendChild(createArrowElement(props.arrowType)); } if (props.animateFill) { tooltip.appendChild(createBackdropElement()); tooltip.setAttribute('data-animatefill', ''); } if (props.inertia) { addInertia(tooltip); } setContent(content, props); tooltip.appendChild(content); popper.appendChild(tooltip); return popper; } /** * Updates the popper element based on the new props */ function updatePopperElement(popper, prevProps, nextProps) { var _getChildren = getChildren(popper), tooltip = _getChildren.tooltip, content = _getChildren.content, backdrop = _getChildren.backdrop, arrow = _getChildren.arrow; popper.style.zIndex = '' + nextProps.zIndex; tooltip.setAttribute('data-size', nextProps.size); tooltip.setAttribute('data-animation', nextProps.animation); tooltip.style.maxWidth = nextProps.maxWidth + (typeof nextProps.maxWidth === 'number' ? 'px' : ''); if (nextProps.role) { popper.setAttribute('role', nextProps.role); } else { popper.removeAttribute('role'); } if (prevProps.content !== nextProps.content) { setContent(content, nextProps); } // animateFill if (!prevProps.animateFill && nextProps.animateFill) { tooltip.appendChild(createBackdropElement()); tooltip.setAttribute('data-animatefill', ''); } else if (prevProps.animateFill && !nextProps.animateFill) { tooltip.removeChild(backdrop); tooltip.removeAttribute('data-animatefill'); } // arrow if (!prevProps.arrow && nextProps.arrow) { tooltip.appendChild(createArrowElement(nextProps.arrowType)); } else if (prevProps.arrow && !nextProps.arrow) { tooltip.removeChild(arrow); } // arrowType if (prevProps.arrow && nextProps.arrow && prevProps.arrowType !== nextProps.arrowType) { tooltip.replaceChild(createArrowElement(nextProps.arrowType), arrow); } // interactive if (!prevProps.interactive && nextProps.interactive) { addInteractive(popper, tooltip); } else if (prevProps.interactive && !nextProps.interactive) { removeInteractive(popper, tooltip); } // inertia if (!prevProps.inertia && nextProps.inertia) { addInertia(tooltip); } else if (prevProps.inertia && !nextProps.inertia) { removeInertia(tooltip); } // theme if (prevProps.theme !== nextProps.theme) { updateTheme(tooltip, 'remove', prevProps.theme); updateTheme(tooltip, 'add', nextProps.theme); } } /** * Hides all visible poppers on the document */ function hideAll() { var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, excludedReferenceOrInstance = _ref.exclude, duration = _ref.duration; arrayFrom(document.querySelectorAll(POPPER_SELECTOR)).forEach(function (popper) { var instance = popper._tippy; if (instance) { var isExcluded = false; if (excludedReferenceOrInstance) { isExcluded = isReferenceElement(excludedReferenceOrInstance) ? instance.reference === excludedReferenceOrInstance : popper === excludedReferenceOrInstance.popper; } if (!isExcluded) { instance.hide(duration); } } }); } /** * Determines if the mouse cursor is outside of the popper's interactive border * region */ function isCursorOutsideInteractiveBorder(popperPlacement, popperRect, event, props) { if (!popperPlacement) { return true; } var x = event.clientX, y = event.clientY; var interactiveBorder = props.interactiveBorder, distance = props.distance; var exceedsTop = popperRect.top - y > (popperPlacement === 'top' ? interactiveBorder + distance : interactiveBorder); var exceedsBottom = y - popperRect.bottom > (popperPlacement === 'bottom' ? interactiveBorder + distance : interactiveBorder); var exceedsLeft = popperRect.left - x > (popperPlacement === 'left' ? interactiveBorder + distance : interactiveBorder); var exceedsRight = x - popperRect.right > (popperPlacement === 'right' ? interactiveBorder + distance : interactiveBorder); return exceedsTop || exceedsBottom || exceedsLeft || exceedsRight; } /** * Returns the distance offset, taking into account the default offset due to * the transform: translate() rule (10px) in CSS */ function getOffsetDistanceInPx(distance) { return -(distance - 10) + 'px'; } var idCounter = 1; // Workaround for IE11's lack of new MouseEvent constructor var mouseMoveListeners = []; /** * Creates and returns a Tippy object. We're using a closure pattern instead of * a class so that the exposed object API is clean without private members * prefixed with `_`. */ function createTippy(reference, collectionProps) { var props = evaluateProps(reference, collectionProps); // If the reference shouldn't have multiple tippys, return null early if (!props.multiple && reference._tippy) { return null; } /* ======================= 🔒 Private members 🔒 ======================= */ var lastTriggerEventType; var lastMouseMoveEvent; var showTimeoutId; var hideTimeoutId; var scheduleHideAnimationFrameId; var isScheduledToShow = false; var isBeingDestroyed = false; var previousPlacement; var wasVisibleDuringPreviousUpdate = false; var hasMountCallbackRun = false; var currentMountCallback; var currentTransitionEndListener; var listeners = []; var currentComputedPadding; var debouncedOnMouseMove = debounce(onMouseMove, props.interactiveDebounce); /* ======================= 🔑 Public members 🔑 ======================= */ var id = idCounter++; var popper = createPopperElement(id, props); var popperChildren = getChildren(popper); var popperInstance = null; var state = { // Is the instance currently enabled? isEnabled: true, // Is the tippy currently showing and not transitioning out? isVisible: false, // Has the instance been destroyed? isDestroyed: false, // Is the tippy currently mounted to the DOM? isMounted: false, // Has the tippy finished transitioning in? isShown: false }; var instance = { // properties id: id, reference: reference, popper: popper, popperChildren: popperChildren, popperInstance: popperInstance, props: props, state: state, // methods clearDelayTimeouts: clearDelayTimeouts, set: set, setContent: setContent, show: show, hide: hide, enable: enable, disable: disable, destroy: destroy /* ==================== Initial instance mutations =================== */ }; reference._tippy = instance; popper._tippy = instance; addTriggersToReference(); if (!props.lazy) { createPopperInstance(); } if (props.showOnInit) { scheduleShow(); } // Ensure the event listeners target can receive focus if (props.a11y && !props.target && !canReceiveFocus(getEventListenersTarget())) { getEventListenersTarget().setAttribute('tabindex', '0'); } // Prevent a tippy with a delay from hiding if the cursor left then returned // before it started hiding popper.addEventListener('mouseenter', function (event) { if (instance.props.interactive && instance.state.isVisible && lastTriggerEventType === 'mouseenter') { // We don't want props.onTrigger() to be called here, since the `event` // object is not related to the reference element scheduleShow(event, true); } }); popper.addEventListener('mouseleave', function () { if (instance.props.interactive && lastTriggerEventType === 'mouseenter') { document.addEventListener('mousemove', debouncedOnMouseMove); } }); return instance; /* ======================= 🔒 Private methods 🔒 ======================= */ /** * Removes the follow cursor listener */ function removeFollowCursorListener() { document.removeEventListener('mousemove', positionVirtualReferenceNearCursor); } /** * Cleans up interactive mouse listeners */ function cleanupInteractiveMouseListeners() { document.body.removeEventListener('mouseleave', scheduleHide); document.removeEventListener('mousemove', debouncedOnMouseMove); mouseMoveListeners = mouseMoveListeners.filter(function (listener) { return listener !== debouncedOnMouseMove; }); } /** * Returns correct target used for event listeners */ function getEventListenersTarget() { return instance.props.triggerTarget || reference; } /** * Adds the document click event listener for the instance */ function addDocumentClickListener() { document.addEventListener('click', onDocumentClick, true); } /** * Removes the document click event listener for the instance */ function removeDocumentClickListener() { document.removeEventListener('click', onDocumentClick, true); } /** * Returns transitionable inner elements used in show/hide methods */ function getTransitionableElements() { return [instance.popperChildren.tooltip, instance.popperChildren.backdrop, instance.popperChildren.content]; } /** * Determines if the instance is in `followCursor` mode. * NOTE: in v5, touch devices will use `initial` behavior no matter the value. */ function getIsInLooseFollowCursorMode() { var followCursor = instance.props.followCursor; return followCursor && lastTriggerEventType !== 'focus' || isUsingTouch && followCursor === 'initial'; } /** * Updates the tooltip's position on each animation frame */ function makeSticky() { setTransitionDuration([popper], isIE ? 0 : instance.props.updateDuration); var prevRefRect = reference.getBoundingClientRect(); function updatePosition() { var currentRefRect = reference.getBoundingClientRect(); // Only schedule an update if the reference rect has changed if (prevRefRect.top !== currentRefRect.top || prevRefRect.right !== currentRefRect.right || prevRefRect.bottom !== currentRefRect.bottom || prevRefRect.left !== currentRefRect.left) { instance.popperInstance.scheduleUpdate(); } prevRefRect = currentRefRect; if (instance.state.isMounted) { requestAnimationFrame(updatePosition); } } updatePosition(); } /** * Invokes a callback once the tooltip has fully transitioned out */ function onTransitionedOut(duration, callback) { onTransitionEnd(duration, function () { if (!instance.state.isVisible && popper.parentNode && popper.parentNode.contains(popper)) { callback(); } }); } /** * Invokes a callback once the tooltip has fully transitioned in */ function onTransitionedIn(duration, callback) { onTransitionEnd(duration, callback); } /** * Invokes a callback once the tooltip's CSS transition ends */ function onTransitionEnd(duration, callback) { var tooltip = instance.popperChildren.tooltip; /** * Listener added as the `transitionend` handler */ function listener(event) { if (event.target === tooltip) { updateTransitionEndListener(tooltip, 'remove', listener); callback(); } } // Make callback synchronous if duration is 0 // `transitionend` won't fire otherwise if (duration === 0) { return callback(); } updateTransitionEndListener(tooltip, 'remove', currentTransitionEndListener); updateTransitionEndListener(tooltip, 'add', listener); currentTransitionEndListener = listener; } /** * Adds an event listener to the reference and stores it in `listeners` */ function on(eventType, handler) { var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; getEventListenersTarget().addEventListener(eventType, handler, options); listeners.push({ eventType: eventType, handler: handler, options: options }); } /** * Adds event listeners to the reference based on the `trigger` prop */ function addTriggersToReference() { if (instance.props.touchHold && !instance.props.target) { on('touchstart', onTrigger, PASSIVE); on('touchend', onMouseLeave, PASSIVE); } instance.props.trigger.trim().split(' ').forEach(function (eventType) { if (eventType === 'manual') { return; } // Non-delegates if (!instance.props.target) { on(eventType, onTrigger); switch (eventType) { case 'mouseenter': on('mouseleave', onMouseLeave); break; case 'focus': on(isIE ? 'focusout' : 'blur', onBlur); break; } } else { // Delegates switch (eventType) { case 'mouseenter': on('mouseover', onDelegateShow); on('mouseout', onDelegateHide); break; case 'focus': on('focusin', onDelegateShow); on('focusout', onDelegateHide); break; case 'click': on(eventType, onDelegateShow); break; } } }); } /** * Removes event listeners from the reference */ function removeTriggersFromReference() { listeners.forEach(function (_ref) { var eventType = _ref.eventType, handler = _ref.handler, options = _ref.options; getEventListenersTarget().removeEventListener(eventType, handler, options); }); listeners = []; } /** * Positions the virtual reference near the cursor */ function positionVirtualReferenceNearCursor(event) { var _lastMouseMoveEvent = lastMouseMoveEvent = event, x = _lastMouseMoveEvent.clientX, y = _lastMouseMoveEvent.clientY; // Gets set once popperInstance `onCreate` has been called if (!currentComputedPadding) { return; } // If the instance is interactive, avoid updating the position unless it's // over the reference element var isCursorOverReference = closestCallback(event.target, function (el) { return el === reference; }); var rect = reference.getBoundingClientRect(); var followCursor = instance.props.followCursor; var isHorizontal = followCursor === 'horizontal'; var isVertical = followCursor === 'vertical'; // The virtual reference needs some size to prevent itself from overflowing var isVerticalPlacement = includes(['top', 'bottom'], getBasicPlacement(popper)); var fullPlacement = popper.getAttribute(PLACEMENT_ATTRIBUTE); var isVariation = fullPlacement ? !!fullPlacement.split('-')[1] : false; var size = isVerticalPlacement ? popper.offsetWidth : popper.offsetHeight; var halfSize = size / 2; var verticalIncrease = isVerticalPlacement ? 0 : isVariation ? size : halfSize; var horizontalIncrease = isVerticalPlacement ? isVariation ? size : halfSize : 0; if (isCursorOverReference || !instance.props.interactive) { instance.popperInstance.reference = _extends({}, instance.popperInstance.reference, { // This will exist in next Popper.js feature release to fix #532 // @ts-ignore referenceNode: reference, // These `client` values don't get used by Popper.js if they are 0 clientWidth: 0, clientHeight: 0, getBoundingClientRect: function getBoundingClientRect() { return { width: isVerticalPlacement ? size : 0, height: isVerticalPlacement ? 0 : size, top: (isHorizontal ? rect.top : y) - verticalIncrease, bottom: (isHorizontal ? rect.bottom : y) + verticalIncrease, left: (isVertical ? rect.left : x) - horizontalIncrease, right: (isVertical ? rect.right : x) + horizontalIncrease }; } }); instance.popperInstance.update(); } if (followCursor === 'initial' && instance.state.isVisible) { removeFollowCursorListener(); } } /** * Creates the tippy instance for a delegate when it's been triggered */ function createDelegateChildTippy(event) { if (event) { var targetEl = closest(event.target, instance.props.target); if (targetEl && !targetEl._tippy) { createTippy(targetEl, _extends({}, instance.props, { content: invokeWithArgsOrReturn(collectionProps.content, [targetEl]), appendTo: collectionProps.appendTo, target: '', showOnInit: true })); } } } /** * Event listener invoked upon trigger */ function onTrigger(event) { if (!instance.state.isEnabled || isEventListenerStopped(event)) { return; } if (!instance.state.isVisible) { lastTriggerEventType = event.type; if (event instanceof MouseEvent) { lastMouseMoveEvent = event; // If scrolling, `mouseenter` events can be fired if the cursor lands // over a new target, but `mousemove` events don't get fired. This // causes interactive tooltips to get stuck open until the cursor is // moved mouseMoveListeners.forEach(function (listener) { return listener(event); }); } } // Toggle show/hide when clicking click-triggered tooltips if (event.type === 'click' && instance.props.hideOnClick !== false && instance.state.isVisible) { scheduleHide(); } else { scheduleShow(event); } } /** * Event listener used for interactive tooltips to detect when they should * hide */ function onMouseMove(event) { var isCursorOverPopper = closest(event.target, POPPER_SELECTOR) === popper; var isCursorOverReference = closestCallback(event.target, function (el) { return el === reference; }); if (isCursorOverPopper || isCursorOverReference) { return; } if (isCursorOutsideInteractiveBorder(getBasicPlacement(popper), popper.getBoundingClientRect(), event, instance.props)) { cleanupInteractiveMouseListeners(); scheduleHide(); } } /** * Event listener invoked upon mouseleave */ function onMouseLeave(event) { if (isEventListenerStopped(event)) { return; } if (instance.props.interactive) { document.body.addEventListener('mouseleave', scheduleHide); document.addEventListener('mousemove', debouncedOnMouseMove); mouseMoveListeners.push(debouncedOnMouseMove); return; } scheduleHide(); } /** * Event listener invoked upon blur */ function onBlur(event) { if (event.target !== getEventListenersTarget()) { return; } if (instance.props.interactive && event.relatedTarget && popper.contains(event.relatedTarget)) { return; } scheduleHide(); } /** * Event listener invoked when a child target is triggered */ function onDelegateShow(event) { if (closest(event.target, instance.props.target)) { scheduleShow(event); } } /** * Event listener invoked when a child target should hide */ function onDelegateHide(event) { if (closest(event.target, instance.props.target)) { scheduleHide(); } } /** * Determines if an event listener should stop further execution due to the * `touchHold` option */ function isEventListenerStopped(event) { var supportsTouch = 'ontouchstart' in window; var isTouchEvent = includes(event.type, 'touch'); var touchHold = instance.props.touchHold; return supportsTouch && isUsingTouch && touchHold && !isTouchEvent || isUsingTouch && !touchHold && isTouchEvent; } /** * Runs the mount callback */