UNPKG

tippy.js

Version:

The complete tooltip, popover, dropdown, and menu solution for the web

1,583 lines (1,329 loc) 77.9 kB
/**! * tippy.js v6.3.7 * (c) 2017-2021 atomiks * MIT License */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('@popperjs/core')) : typeof define === 'function' && define.amd ? define(['@popperjs/core'], factory) : (global = global || self, global.tippy = factory(global.Popper)); }(this, (function (core) { 'use strict'; var ROUND_ARROW = '<svg width="16" height="6" xmlns="http://www.w3.org/2000/svg"><path d="M0 6s1.796-.013 4.67-3.615C5.851.9 6.93.006 8 0c1.07-.006 2.148.887 3.343 2.385C14.233 6.005 16 6 16 6H0z"></svg>'; var BOX_CLASS = "tippy-box"; var CONTENT_CLASS = "tippy-content"; var BACKDROP_CLASS = "tippy-backdrop"; var ARROW_CLASS = "tippy-arrow"; var SVG_ARROW_CLASS = "tippy-svg-arrow"; var TOUCH_OPTIONS = { passive: true, capture: true }; var TIPPY_DEFAULT_APPEND_TO = function TIPPY_DEFAULT_APPEND_TO() { return document.body; }; function hasOwnProperty(obj, key) { return {}.hasOwnProperty.call(obj, key); } function getValueAtIndexOrReturn(value, index, defaultValue) { if (Array.isArray(value)) { var v = value[index]; return v == null ? Array.isArray(defaultValue) ? defaultValue[index] : defaultValue : v; } return value; } function isType(value, type) { var str = {}.toString.call(value); return str.indexOf('[object') === 0 && str.indexOf(type + "]") > -1; } function invokeWithArgsOrReturn(value, args) { return typeof value === 'function' ? value.apply(void 0, args) : value; } 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); }; } function removeProperties(obj, keys) { var clone = Object.assign({}, obj); keys.forEach(function (key) { delete clone[key]; }); return clone; } function splitBySpaces(value) { return value.split(/\s+/).filter(Boolean); } function normalizeToArray(value) { return [].concat(value); } function pushIfUnique(arr, value) { if (arr.indexOf(value) === -1) { arr.push(value); } } function unique(arr) { return arr.filter(function (item, index) { return arr.indexOf(item) === index; }); } function getBasePlacement(placement) { return placement.split('-')[0]; } function arrayFrom(value) { return [].slice.call(value); } function removeUndefinedProps(obj) { return Object.keys(obj).reduce(function (acc, key) { if (obj[key] !== undefined) { acc[key] = obj[key]; } return acc; }, {}); } function div() { return document.createElement('div'); } function isElement(value) { return ['Element', 'Fragment'].some(function (type) { return isType(value, type); }); } function isNodeList(value) { return isType(value, 'NodeList'); } function isMouseEvent(value) { return isType(value, 'MouseEvent'); } function isReferenceElement(value) { return !!(value && value._tippy && value._tippy.reference === value); } function getArrayOfElements(value) { if (isElement(value)) { return [value]; } if (isNodeList(value)) { return arrayFrom(value); } if (Array.isArray(value)) { return value; } return arrayFrom(document.querySelectorAll(value)); } function setTransitionDuration(els, value) { els.forEach(function (el) { if (el) { el.style.transitionDuration = value + "ms"; } }); } function setVisibilityState(els, state) { els.forEach(function (el) { if (el) { el.setAttribute('data-state', state); } }); } function getOwnerDocument(elementOrElements) { var _element$ownerDocumen; var _normalizeToArray = normalizeToArray(elementOrElements), element = _normalizeToArray[0]; // Elements created via a <template> have an ownerDocument with no reference to the body return element != null && (_element$ownerDocumen = element.ownerDocument) != null && _element$ownerDocumen.body ? element.ownerDocument : document; } function isCursorOutsideInteractiveBorder(popperTreeData, event) { var clientX = event.clientX, clientY = event.clientY; return popperTreeData.every(function (_ref) { var popperRect = _ref.popperRect, popperState = _ref.popperState, props = _ref.props; var interactiveBorder = props.interactiveBorder; var basePlacement = getBasePlacement(popperState.placement); var offsetData = popperState.modifiersData.offset; if (!offsetData) { return true; } var topDistance = basePlacement === 'bottom' ? offsetData.top.y : 0; var bottomDistance = basePlacement === 'top' ? offsetData.bottom.y : 0; var leftDistance = basePlacement === 'right' ? offsetData.left.x : 0; var rightDistance = basePlacement === 'left' ? offsetData.right.x : 0; var exceedsTop = popperRect.top - clientY + topDistance > interactiveBorder; var exceedsBottom = clientY - popperRect.bottom - bottomDistance > interactiveBorder; var exceedsLeft = popperRect.left - clientX + leftDistance > interactiveBorder; var exceedsRight = clientX - popperRect.right - rightDistance > interactiveBorder; return exceedsTop || exceedsBottom || exceedsLeft || exceedsRight; }); } function updateTransitionEndListener(box, action, listener) { var method = action + "EventListener"; // some browsers apparently support `transition` (unprefixed) but only fire // `webkitTransitionEnd`... ['transitionend', 'webkitTransitionEnd'].forEach(function (event) { box[method](event, listener); }); } /** * Compared to xxx.contains, this function works for dom structures with shadow * dom */ function actualContains(parent, child) { var target = child; while (target) { var _target$getRootNode; if (parent.contains(target)) { return true; } target = target.getRootNode == null ? void 0 : (_target$getRootNode = target.getRootNode()) == null ? void 0 : _target$getRootNode.host; } return false; } var currentInput = { isTouch: false }; var lastMouseMoveTime = 0; /** * When a `touchstart` event is fired, it's assumed the user is using touch * input. We'll bind a `mousemove` event listener to listen for mouse input in * the future. This way, the `isTouch` property is fully dynamic and will handle * hybrid devices that use a mix of touch + mouse input. */ function onDocumentTouchStart() { if (currentInput.isTouch) { return; } currentInput.isTouch = true; if (window.performance) { document.addEventListener('mousemove', onDocumentMouseMove); } } /** * When two `mousemove` event are fired consecutively within 20ms, it's assumed * the user is using mouse input again. `mousemove` can fire on touch devices as * well, but very rarely that quickly. */ function onDocumentMouseMove() { var now = performance.now(); if (now - lastMouseMoveTime < 20) { currentInput.isTouch = false; document.removeEventListener('mousemove', onDocumentMouseMove); } lastMouseMoveTime = now; } /** * When an element is in focus and has a tippy, leaving the tab/window and * returning causes it to show again. For mouse users this is unexpected, but * for keyboard use it makes sense. * TODO: find a better technique to solve this problem */ function onWindowBlur() { var activeElement = document.activeElement; if (isReferenceElement(activeElement)) { var instance = activeElement._tippy; if (activeElement.blur && !instance.state.isVisible) { activeElement.blur(); } } } function bindGlobalEventListeners() { document.addEventListener('touchstart', onDocumentTouchStart, TOUCH_OPTIONS); window.addEventListener('blur', onWindowBlur); } var isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined'; var isIE11 = isBrowser ? // @ts-ignore !!window.msCrypto : false; function createMemoryLeakWarning(method) { var txt = method === 'destroy' ? 'n already-' : ' '; return [method + "() was called on a" + txt + "destroyed instance. This is a no-op but", 'indicates a potential memory leak.'].join(' '); } function clean(value) { var spacesAndTabs = /[ \t]{2,}/g; var lineStartWithSpaces = /^[ \t]*/gm; return value.replace(spacesAndTabs, ' ').replace(lineStartWithSpaces, '').trim(); } function getDevMessage(message) { return clean("\n %ctippy.js\n\n %c" + clean(message) + "\n\n %c\uD83D\uDC77\u200D This is a development-only message. It will be removed in production.\n "); } function getFormattedMessage(message) { return [getDevMessage(message), // title 'color: #00C584; font-size: 1.3em; font-weight: bold;', // message 'line-height: 1.5', // footer 'color: #a6a095;']; } // Assume warnings and errors never have the same message var visitedMessages; { resetVisitedMessages(); } function resetVisitedMessages() { visitedMessages = new Set(); } function warnWhen(condition, message) { if (condition && !visitedMessages.has(message)) { var _console; visitedMessages.add(message); (_console = console).warn.apply(_console, getFormattedMessage(message)); } } function errorWhen(condition, message) { if (condition && !visitedMessages.has(message)) { var _console2; visitedMessages.add(message); (_console2 = console).error.apply(_console2, getFormattedMessage(message)); } } function validateTargets(targets) { var didPassFalsyValue = !targets; var didPassPlainObject = Object.prototype.toString.call(targets) === '[object Object]' && !targets.addEventListener; errorWhen(didPassFalsyValue, ['tippy() was passed', '`' + String(targets) + '`', 'as its targets (first) argument. Valid types are: String, Element,', 'Element[], or NodeList.'].join(' ')); errorWhen(didPassPlainObject, ['tippy() was passed a plain object which is not supported as an argument', 'for virtual positioning. Use props.getReferenceClientRect instead.'].join(' ')); } var pluginProps = { animateFill: false, followCursor: false, inlinePositioning: false, sticky: false }; var renderProps = { allowHTML: false, animation: 'fade', arrow: true, content: '', inertia: false, maxWidth: 350, role: 'tooltip', theme: '', zIndex: 9999 }; var defaultProps = Object.assign({ appendTo: TIPPY_DEFAULT_APPEND_TO, aria: { content: 'auto', expanded: 'auto' }, delay: 0, duration: [300, 250], getReferenceClientRect: null, hideOnClick: true, ignoreAttributes: false, interactive: false, interactiveBorder: 2, interactiveDebounce: 0, moveTransition: '', offset: [0, 10], onAfterUpdate: function onAfterUpdate() {}, onBeforeUpdate: function onBeforeUpdate() {}, onCreate: function onCreate() {}, onDestroy: function onDestroy() {}, onHidden: function onHidden() {}, onHide: function onHide() {}, onMount: function onMount() {}, onShow: function onShow() {}, onShown: function onShown() {}, onTrigger: function onTrigger() {}, onUntrigger: function onUntrigger() {}, onClickOutside: function onClickOutside() {}, placement: 'top', plugins: [], popperOptions: {}, render: null, showOnCreate: false, touch: true, trigger: 'mouseenter focus', triggerTarget: null }, pluginProps, renderProps); var defaultKeys = Object.keys(defaultProps); var setDefaultProps = function setDefaultProps(partialProps) { /* istanbul ignore else */ { validateProps(partialProps, []); } var keys = Object.keys(partialProps); keys.forEach(function (key) { defaultProps[key] = partialProps[key]; }); }; function getExtendedPassedProps(passedProps) { var plugins = passedProps.plugins || []; var pluginProps = plugins.reduce(function (acc, plugin) { var name = plugin.name, defaultValue = plugin.defaultValue; if (name) { var _name; acc[name] = passedProps[name] !== undefined ? passedProps[name] : (_name = defaultProps[name]) != null ? _name : defaultValue; } return acc; }, {}); return Object.assign({}, passedProps, pluginProps); } function getDataAttributeProps(reference, plugins) { var propKeys = plugins ? Object.keys(getExtendedPassedProps(Object.assign({}, defaultProps, { plugins: plugins }))) : defaultKeys; var props = propKeys.reduce(function (acc, key) { var valueAsString = (reference.getAttribute("data-tippy-" + 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; }, {}); return props; } function evaluateProps(reference, props) { var out = Object.assign({}, props, { content: invokeWithArgsOrReturn(props.content, [reference]) }, props.ignoreAttributes ? {} : getDataAttributeProps(reference, props.plugins)); out.aria = Object.assign({}, defaultProps.aria, out.aria); out.aria = { expanded: out.aria.expanded === 'auto' ? props.interactive : out.aria.expanded, content: out.aria.content === 'auto' ? props.interactive ? null : 'describedby' : out.aria.content }; return out; } function validateProps(partialProps, plugins) { if (partialProps === void 0) { partialProps = {}; } if (plugins === void 0) { plugins = []; } var keys = Object.keys(partialProps); keys.forEach(function (prop) { var nonPluginProps = removeProperties(defaultProps, Object.keys(pluginProps)); var didPassUnknownProp = !hasOwnProperty(nonPluginProps, prop); // Check if the prop exists in `plugins` if (didPassUnknownProp) { didPassUnknownProp = plugins.filter(function (plugin) { return plugin.name === prop; }).length === 0; } warnWhen(didPassUnknownProp, ["`" + prop + "`", "is not a valid prop. You may have spelled it incorrectly, or if it's", 'a plugin, forgot to pass it in an array as props.plugins.', '\n\n', 'All props: https://atomiks.github.io/tippyjs/v6/all-props/\n', 'Plugins: https://atomiks.github.io/tippyjs/v6/plugins/'].join(' ')); }); } var innerHTML = function innerHTML() { return 'innerHTML'; }; function dangerouslySetInnerHTML(element, html) { element[innerHTML()] = html; } function createArrowElement(value) { var arrow = div(); if (value === true) { arrow.className = ARROW_CLASS; } else { arrow.className = SVG_ARROW_CLASS; if (isElement(value)) { arrow.appendChild(value); } else { dangerouslySetInnerHTML(arrow, value); } } return arrow; } function setContent(content, props) { if (isElement(props.content)) { dangerouslySetInnerHTML(content, ''); content.appendChild(props.content); } else if (typeof props.content !== 'function') { if (props.allowHTML) { dangerouslySetInnerHTML(content, props.content); } else { content.textContent = props.content; } } } function getChildren(popper) { var box = popper.firstElementChild; var boxChildren = arrayFrom(box.children); return { box: box, content: boxChildren.find(function (node) { return node.classList.contains(CONTENT_CLASS); }), arrow: boxChildren.find(function (node) { return node.classList.contains(ARROW_CLASS) || node.classList.contains(SVG_ARROW_CLASS); }), backdrop: boxChildren.find(function (node) { return node.classList.contains(BACKDROP_CLASS); }) }; } function render(instance) { var popper = div(); var box = div(); box.className = BOX_CLASS; box.setAttribute('data-state', 'hidden'); box.setAttribute('tabindex', '-1'); var content = div(); content.className = CONTENT_CLASS; content.setAttribute('data-state', 'hidden'); setContent(content, instance.props); popper.appendChild(box); box.appendChild(content); onUpdate(instance.props, instance.props); function onUpdate(prevProps, nextProps) { var _getChildren = getChildren(popper), box = _getChildren.box, content = _getChildren.content, arrow = _getChildren.arrow; if (nextProps.theme) { box.setAttribute('data-theme', nextProps.theme); } else { box.removeAttribute('data-theme'); } if (typeof nextProps.animation === 'string') { box.setAttribute('data-animation', nextProps.animation); } else { box.removeAttribute('data-animation'); } if (nextProps.inertia) { box.setAttribute('data-inertia', ''); } else { box.removeAttribute('data-inertia'); } box.style.maxWidth = typeof nextProps.maxWidth === 'number' ? nextProps.maxWidth + "px" : nextProps.maxWidth; if (nextProps.role) { box.setAttribute('role', nextProps.role); } else { box.removeAttribute('role'); } if (prevProps.content !== nextProps.content || prevProps.allowHTML !== nextProps.allowHTML) { setContent(content, instance.props); } if (nextProps.arrow) { if (!arrow) { box.appendChild(createArrowElement(nextProps.arrow)); } else if (prevProps.arrow !== nextProps.arrow) { box.removeChild(arrow); box.appendChild(createArrowElement(nextProps.arrow)); } } else if (arrow) { box.removeChild(arrow); } } return { popper: popper, onUpdate: onUpdate }; } // Runtime check to identify if the render function is the default one; this // way we can apply default CSS transitions logic and it can be tree-shaken away render.$$tippy = true; var idCounter = 1; var mouseMoveListeners = []; // Used by `hideAll()` var mountedInstances = []; function createTippy(reference, passedProps) { var props = evaluateProps(reference, Object.assign({}, defaultProps, getExtendedPassedProps(removeUndefinedProps(passedProps)))); // =========================================================================== // 🔒 Private members // =========================================================================== var showTimeout; var hideTimeout; var scheduleHideAnimationFrame; var isVisibleFromClick = false; var didHideDueToDocumentMouseDown = false; var didTouchMove = false; var ignoreOnFirstUpdate = false; var lastTriggerEvent; var currentTransitionEndListener; var onFirstUpdate; var listeners = []; var debouncedOnMouseMove = debounce(onMouseMove, props.interactiveDebounce); var currentTarget; // =========================================================================== // 🔑 Public members // =========================================================================== var id = idCounter++; var popperInstance = null; var plugins = unique(props.plugins); 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: div(), popperInstance: popperInstance, props: props, state: state, plugins: plugins, // methods clearDelayTimeouts: clearDelayTimeouts, setProps: setProps, setContent: setContent, show: show, hide: hide, hideWithInteractivity: hideWithInteractivity, enable: enable, disable: disable, unmount: unmount, destroy: destroy }; // TODO: Investigate why this early return causes a TDZ error in the tests — // it doesn't seem to happen in the browser /* istanbul ignore if */ if (!props.render) { { errorWhen(true, 'render() function has not been supplied.'); } return instance; } // =========================================================================== // Initial mutations // =========================================================================== var _props$render = props.render(instance), popper = _props$render.popper, onUpdate = _props$render.onUpdate; popper.setAttribute('data-tippy-root', ''); popper.id = "tippy-" + instance.id; instance.popper = popper; reference._tippy = instance; popper._tippy = instance; var pluginsHooks = plugins.map(function (plugin) { return plugin.fn(instance); }); var hasAriaExpanded = reference.hasAttribute('aria-expanded'); addListeners(); handleAriaExpandedAttribute(); handleStyles(); invokeHook('onCreate', [instance]); if (props.showOnCreate) { scheduleShow(); } // Prevent a tippy with a delay from hiding if the cursor left then returned // before it started hiding popper.addEventListener('mouseenter', function () { if (instance.props.interactive && instance.state.isVisible) { instance.clearDelayTimeouts(); } }); popper.addEventListener('mouseleave', function () { if (instance.props.interactive && instance.props.trigger.indexOf('mouseenter') >= 0) { getDocument().addEventListener('mousemove', debouncedOnMouseMove); } }); return instance; // =========================================================================== // 🔒 Private methods // =========================================================================== function getNormalizedTouchSettings() { var touch = instance.props.touch; return Array.isArray(touch) ? touch : [touch, 0]; } function getIsCustomTouchBehavior() { return getNormalizedTouchSettings()[0] === 'hold'; } function getIsDefaultRenderFn() { var _instance$props$rende; // @ts-ignore return !!((_instance$props$rende = instance.props.render) != null && _instance$props$rende.$$tippy); } function getCurrentTarget() { return currentTarget || reference; } function getDocument() { var parent = getCurrentTarget().parentNode; return parent ? getOwnerDocument(parent) : document; } function getDefaultTemplateChildren() { return getChildren(popper); } function getDelay(isShow) { // For touch or keyboard input, force `0` delay for UX reasons // Also if the instance is mounted but not visible (transitioning out), // ignore delay if (instance.state.isMounted && !instance.state.isVisible || currentInput.isTouch || lastTriggerEvent && lastTriggerEvent.type === 'focus') { return 0; } return getValueAtIndexOrReturn(instance.props.delay, isShow ? 0 : 1, defaultProps.delay); } function handleStyles(fromHide) { if (fromHide === void 0) { fromHide = false; } popper.style.pointerEvents = instance.props.interactive && !fromHide ? '' : 'none'; popper.style.zIndex = "" + instance.props.zIndex; } function invokeHook(hook, args, shouldInvokePropsHook) { if (shouldInvokePropsHook === void 0) { shouldInvokePropsHook = true; } pluginsHooks.forEach(function (pluginHooks) { if (pluginHooks[hook]) { pluginHooks[hook].apply(pluginHooks, args); } }); if (shouldInvokePropsHook) { var _instance$props; (_instance$props = instance.props)[hook].apply(_instance$props, args); } } function handleAriaContentAttribute() { var aria = instance.props.aria; if (!aria.content) { return; } var attr = "aria-" + aria.content; var id = popper.id; var nodes = normalizeToArray(instance.props.triggerTarget || reference); nodes.forEach(function (node) { var currentValue = node.getAttribute(attr); if (instance.state.isVisible) { node.setAttribute(attr, currentValue ? currentValue + " " + id : id); } else { var nextValue = currentValue && currentValue.replace(id, '').trim(); if (nextValue) { node.setAttribute(attr, nextValue); } else { node.removeAttribute(attr); } } }); } function handleAriaExpandedAttribute() { if (hasAriaExpanded || !instance.props.aria.expanded) { return; } var nodes = normalizeToArray(instance.props.triggerTarget || reference); nodes.forEach(function (node) { if (instance.props.interactive) { node.setAttribute('aria-expanded', instance.state.isVisible && node === getCurrentTarget() ? 'true' : 'false'); } else { node.removeAttribute('aria-expanded'); } }); } function cleanupInteractiveMouseListeners() { getDocument().removeEventListener('mousemove', debouncedOnMouseMove); mouseMoveListeners = mouseMoveListeners.filter(function (listener) { return listener !== debouncedOnMouseMove; }); } function onDocumentPress(event) { // Moved finger to scroll instead of an intentional tap outside if (currentInput.isTouch) { if (didTouchMove || event.type === 'mousedown') { return; } } var actualTarget = event.composedPath && event.composedPath()[0] || event.target; // Clicked on interactive popper if (instance.props.interactive && actualContains(popper, actualTarget)) { return; } // Clicked on the event listeners target if (normalizeToArray(instance.props.triggerTarget || reference).some(function (el) { return actualContains(el, actualTarget); })) { if (currentInput.isTouch) { return; } if (instance.state.isVisible && instance.props.trigger.indexOf('click') >= 0) { return; } } else { invokeHook('onClickOutside', [instance, event]); } if (instance.props.hideOnClick === true) { instance.clearDelayTimeouts(); instance.hide(); // `mousedown` event is fired right before `focus` if pressing the // currentTarget. This lets a tippy with `focus` trigger know that it // should not show didHideDueToDocumentMouseDown = true; setTimeout(function () { didHideDueToDocumentMouseDown = false; }); // The listener gets added in `scheduleShow()`, but this may be hiding it // before it shows, and hide()'s early bail-out behavior can prevent it // from being cleaned up if (!instance.state.isMounted) { removeDocumentPress(); } } } function onTouchMove() { didTouchMove = true; } function onTouchStart() { didTouchMove = false; } function addDocumentPress() { var doc = getDocument(); doc.addEventListener('mousedown', onDocumentPress, true); doc.addEventListener('touchend', onDocumentPress, TOUCH_OPTIONS); doc.addEventListener('touchstart', onTouchStart, TOUCH_OPTIONS); doc.addEventListener('touchmove', onTouchMove, TOUCH_OPTIONS); } function removeDocumentPress() { var doc = getDocument(); doc.removeEventListener('mousedown', onDocumentPress, true); doc.removeEventListener('touchend', onDocumentPress, TOUCH_OPTIONS); doc.removeEventListener('touchstart', onTouchStart, TOUCH_OPTIONS); doc.removeEventListener('touchmove', onTouchMove, TOUCH_OPTIONS); } function onTransitionedOut(duration, callback) { onTransitionEnd(duration, function () { if (!instance.state.isVisible && popper.parentNode && popper.parentNode.contains(popper)) { callback(); } }); } function onTransitionedIn(duration, callback) { onTransitionEnd(duration, callback); } function onTransitionEnd(duration, callback) { var box = getDefaultTemplateChildren().box; function listener(event) { if (event.target === box) { updateTransitionEndListener(box, 'remove', listener); callback(); } } // Make callback synchronous if duration is 0 // `transitionend` won't fire otherwise if (duration === 0) { return callback(); } updateTransitionEndListener(box, 'remove', currentTransitionEndListener); updateTransitionEndListener(box, 'add', listener); currentTransitionEndListener = listener; } function on(eventType, handler, options) { if (options === void 0) { options = false; } var nodes = normalizeToArray(instance.props.triggerTarget || reference); nodes.forEach(function (node) { node.addEventListener(eventType, handler, options); listeners.push({ node: node, eventType: eventType, handler: handler, options: options }); }); } function addListeners() { if (getIsCustomTouchBehavior()) { on('touchstart', onTrigger, { passive: true }); on('touchend', onMouseLeave, { passive: true }); } splitBySpaces(instance.props.trigger).forEach(function (eventType) { if (eventType === 'manual') { return; } on(eventType, onTrigger); switch (eventType) { case 'mouseenter': on('mouseleave', onMouseLeave); break; case 'focus': on(isIE11 ? 'focusout' : 'blur', onBlurOrFocusOut); break; case 'focusin': on('focusout', onBlurOrFocusOut); break; } }); } function removeListeners() { listeners.forEach(function (_ref) { var node = _ref.node, eventType = _ref.eventType, handler = _ref.handler, options = _ref.options; node.removeEventListener(eventType, handler, options); }); listeners = []; } function onTrigger(event) { var _lastTriggerEvent; var shouldScheduleClickHide = false; if (!instance.state.isEnabled || isEventListenerStopped(event) || didHideDueToDocumentMouseDown) { return; } var wasFocused = ((_lastTriggerEvent = lastTriggerEvent) == null ? void 0 : _lastTriggerEvent.type) === 'focus'; lastTriggerEvent = event; currentTarget = event.currentTarget; handleAriaExpandedAttribute(); if (!instance.state.isVisible && isMouseEvent(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.trigger.indexOf('mouseenter') < 0 || isVisibleFromClick) && instance.props.hideOnClick !== false && instance.state.isVisible) { shouldScheduleClickHide = true; } else { scheduleShow(event); } if (event.type === 'click') { isVisibleFromClick = !shouldScheduleClickHide; } if (shouldScheduleClickHide && !wasFocused) { scheduleHide(event); } } function onMouseMove(event) { var target = event.target; var isCursorOverReferenceOrPopper = getCurrentTarget().contains(target) || popper.contains(target); if (event.type === 'mousemove' && isCursorOverReferenceOrPopper) { return; } var popperTreeData = getNestedPopperTree().concat(popper).map(function (popper) { var _instance$popperInsta; var instance = popper._tippy; var state = (_instance$popperInsta = instance.popperInstance) == null ? void 0 : _instance$popperInsta.state; if (state) { return { popperRect: popper.getBoundingClientRect(), popperState: state, props: props }; } return null; }).filter(Boolean); if (isCursorOutsideInteractiveBorder(popperTreeData, event)) { cleanupInteractiveMouseListeners(); scheduleHide(event); } } function onMouseLeave(event) { var shouldBail = isEventListenerStopped(event) || instance.props.trigger.indexOf('click') >= 0 && isVisibleFromClick; if (shouldBail) { return; } if (instance.props.interactive) { instance.hideWithInteractivity(event); return; } scheduleHide(event); } function onBlurOrFocusOut(event) { if (instance.props.trigger.indexOf('focusin') < 0 && event.target !== getCurrentTarget()) { return; } // If focus was moved to within the popper if (instance.props.interactive && event.relatedTarget && popper.contains(event.relatedTarget)) { return; } scheduleHide(event); } function isEventListenerStopped(event) { return currentInput.isTouch ? getIsCustomTouchBehavior() !== event.type.indexOf('touch') >= 0 : false; } function createPopperInstance() { destroyPopperInstance(); var _instance$props2 = instance.props, popperOptions = _instance$props2.popperOptions, placement = _instance$props2.placement, offset = _instance$props2.offset, getReferenceClientRect = _instance$props2.getReferenceClientRect, moveTransition = _instance$props2.moveTransition; var arrow = getIsDefaultRenderFn() ? getChildren(popper).arrow : null; var computedReference = getReferenceClientRect ? { getBoundingClientRect: getReferenceClientRect, contextElement: getReferenceClientRect.contextElement || getCurrentTarget() } : reference; var tippyModifier = { name: '$$tippy', enabled: true, phase: 'beforeWrite', requires: ['computeStyles'], fn: function fn(_ref2) { var state = _ref2.state; if (getIsDefaultRenderFn()) { var _getDefaultTemplateCh = getDefaultTemplateChildren(), box = _getDefaultTemplateCh.box; ['placement', 'reference-hidden', 'escaped'].forEach(function (attr) { if (attr === 'placement') { box.setAttribute('data-placement', state.placement); } else { if (state.attributes.popper["data-popper-" + attr]) { box.setAttribute("data-" + attr, ''); } else { box.removeAttribute("data-" + attr); } } }); state.attributes.popper = {}; } } }; var modifiers = [{ name: 'offset', options: { offset: offset } }, { name: 'preventOverflow', options: { padding: { top: 2, bottom: 2, left: 5, right: 5 } } }, { name: 'flip', options: { padding: 5 } }, { name: 'computeStyles', options: { adaptive: !moveTransition } }, tippyModifier]; if (getIsDefaultRenderFn() && arrow) { modifiers.push({ name: 'arrow', options: { element: arrow, padding: 3 } }); } modifiers.push.apply(modifiers, (popperOptions == null ? void 0 : popperOptions.modifiers) || []); instance.popperInstance = core.createPopper(computedReference, popper, Object.assign({}, popperOptions, { placement: placement, onFirstUpdate: onFirstUpdate, modifiers: modifiers })); } function destroyPopperInstance() { if (instance.popperInstance) { instance.popperInstance.destroy(); instance.popperInstance = null; } } function mount() { var appendTo = instance.props.appendTo; var parentNode; // By default, we'll append the popper to the triggerTargets's parentNode so // it's directly after the reference element so the elements inside the // tippy can be tabbed to // If there are clipping issues, the user can specify a different appendTo // and ensure focus management is handled correctly manually var node = getCurrentTarget(); if (instance.props.interactive && appendTo === TIPPY_DEFAULT_APPEND_TO || appendTo === 'parent') { parentNode = node.parentNode; } else { parentNode = invokeWithArgsOrReturn(appendTo, [node]); } // The popper element needs to exist on the DOM before its position can be // updated as Popper needs to read its dimensions if (!parentNode.contains(popper)) { parentNode.appendChild(popper); } instance.state.isMounted = true; createPopperInstance(); /* istanbul ignore else */ { // Accessibility check warnWhen(instance.props.interactive && appendTo === defaultProps.appendTo && node.nextElementSibling !== popper, ['Interactive tippy element may not be accessible via keyboard', 'navigation because it is not directly after the reference element', 'in the DOM source order.', '\n\n', 'Using a wrapper <div> or <span> tag around the reference element', 'solves this by creating a new parentNode context.', '\n\n', 'Specifying `appendTo: document.body` silences this warning, but it', 'assumes you are using a focus management solution to handle', 'keyboard navigation.', '\n\n', 'See: https://atomiks.github.io/tippyjs/v6/accessibility/#interactivity'].join(' ')); } } function getNestedPopperTree() { return arrayFrom(popper.querySelectorAll('[data-tippy-root]')); } function scheduleShow(event) { instance.clearDelayTimeouts(); if (event) { invokeHook('onTrigger', [instance, event]); } addDocumentPress(); var delay = getDelay(true); var _getNormalizedTouchSe = getNormalizedTouchSettings(), touchValue = _getNormalizedTouchSe[0], touchDelay = _getNormalizedTouchSe[1]; if (currentInput.isTouch && touchValue === 'hold' && touchDelay) { delay = touchDelay; } if (delay) { showTimeout = setTimeout(function () { instance.show(); }, delay); } else { instance.show(); } } function scheduleHide(event) { instance.clearDelayTimeouts(); invokeHook('onUntrigger', [instance, event]); if (!instance.state.isVisible) { removeDocumentPress(); return; } // For interactive tippies, scheduleHide is added to a document.body handler // from onMouseLeave so must intercept scheduled hides from mousemove/leave // events when trigger contains mouseenter and click, and the tip is // currently shown as a result of a click. if (instance.props.trigger.indexOf('mouseenter') >= 0 && instance.props.trigger.indexOf('click') >= 0 && ['mouseleave', 'mousemove'].indexOf(event.type) >= 0 && isVisibleFromClick) { return; } var delay = getDelay(false); if (delay) { hideTimeout = setTimeout(function () { if (instance.state.isVisible) { instance.hide(); } }, delay); } else { // Fixes a `transitionend` problem when it fires 1 frame too // late sometimes, we don't want hide() to be called. scheduleHideAnimationFrame = requestAnimationFrame(function () { instance.hide(); }); } } // =========================================================================== // 🔑 Public methods // =========================================================================== function enable() { instance.state.isEnabled = true; } function disable() { // Disabling the instance should also hide it // https://github.com/atomiks/tippy.js-react/issues/106 instance.hide(); instance.state.isEnabled = false; } function clearDelayTimeouts() { clearTimeout(showTimeout); clearTimeout(hideTimeout); cancelAnimationFrame(scheduleHideAnimationFrame); } function setProps(partialProps) { /* istanbul ignore else */ { warnWhen(instance.state.isDestroyed, createMemoryLeakWarning('setProps')); } if (instance.state.isDestroyed) { return; } invokeHook('onBeforeUpdate', [instance, partialProps]); removeListeners(); var prevProps = instance.props; var nextProps = evaluateProps(reference, Object.assign({}, prevProps, removeUndefinedProps(partialProps), { ignoreAttributes: true })); instance.props = nextProps; addListeners(); if (prevProps.interactiveDebounce !== nextProps.interactiveDebounce) { cleanupInteractiveMouseListeners(); debouncedOnMouseMove = debounce(onMouseMove, nextProps.interactiveDebounce); } // Ensure stale aria-expanded attributes are removed if (prevProps.triggerTarget && !nextProps.triggerTarget) { normalizeToArray(prevProps.triggerTarget).forEach(function (node) { node.removeAttribute('aria-expanded'); }); } else if (nextProps.triggerTarget) { reference.removeAttribute('aria-expanded'); } handleAriaExpandedAttribute(); handleStyles(); if (onUpdate) { onUpdate(prevProps, nextProps); } if (instance.popperInstance) { createPopperInstance(); // Fixes an issue with nested tippies if they are all getting re-rendered, // and the nested ones get re-rendered first. // https://github.com/atomiks/tippyjs-react/issues/177 // TODO: find a cleaner / more efficient solution(!) getNestedPopperTree().forEach(function (nestedPopper) { // React (and other UI libs likely) requires a rAF wrapper as it flushes // its work in one requestAnimationFrame(nestedPopper._tippy.popperInstance.forceUpdate); }); } invokeHook('onAfterUpdate', [instance, partialProps]); } function setContent(content) { instance.setProps({ content: content }); } function show() { /* istanbul ignore else */ { warnWhen(instance.state.isDestroyed, createMemoryLeakWarning('show')); } // Early bail-out var isAlreadyVisible = instance.state.isVisible; var isDestroyed = instance.state.isDestroyed; var isDisabled = !instance.state.isEnabled; var isTouchAndTouchDisabled = currentInput.isTouch && !instance.props.touch; var duration = getValueAtIndexOrReturn(instance.props.duration, 0, defaultProps.duration); if (isAlreadyVisible || isDestroyed || isDisabled || isTouchAndTouchDisabled) { return; } // Normalize `disabled` behavior across browsers. // Firefox allows events on disabled elements, but Chrome doesn't. // Using a wrapper element (i.e. <span>) is recommended. if (getCurrentTarget().hasAttribute('disabled')) { return; } invokeHook('onShow', [instance], false); if (instance.props.onShow(instance) === false) { return; } instance.state.isVisible = true; if (getIsDefaultRenderFn()) { popper.style.visibility = 'visible'; } handleStyles(); addDocumentPress(); if (!instance.state.isMounted) { popper.style.transition = 'none'; } // If flipping to the opposite side after hiding at least once, the // animation will use the wrong placement without resetting the duration if (getIsDefaultRenderFn()) { var _getDefaultTemplateCh2 = getDefaultTemplateChildren(), box = _getDefaultTemplateCh2.box, content = _getDefaultTemplateCh2.content; setTransitionDuration([box, content], 0); } onFirstUpdate = function onFirstUpdate() { var _instance$popperInsta2; if (!instance.state.isVisible || ignoreOnFirstUpdate) { return; } ignoreOnFirstUpdate = true; // reflow void popper.offsetHeight; popper.style.transition = instance.props.moveTransition; if (getIsDefaultRenderFn() && instance.props.animation) { var _getDefaultTemplateCh3 = getDefaultTemplateChildren(), _box = _getDefaultTemplateCh3.box, _content = _getDefaultTemplateCh3.content; setTransitionDuration([_box, _content], duration); setVisibilityState([_box, _content], 'visible'); } handleAriaContentAttribute(); handleAriaExpandedAttribute(); pushIfUnique(mountedInstances, instance); // certain modifiers (e.g. `maxSize`) require a second update after the // popper has been positioned for the first time (_instance$popperInsta2 = instance.popperInstance) == null ? void 0 : _instance$popperInsta2.forceUpdate(); invokeHook('onMount', [instance]); if (instance.props.animation && getIsDefaultRenderFn()) { onTransitionedIn(duration, function () { instance.state.isShown = true; invokeHook('onShown', [instance]); }); } }; mount(); } function hide() { /* istanbul ignore else */ { warnWhen(instance.state.isDestroyed, createMemoryLeakWarning('hide')); } // Early bail-out var isAlreadyHidden = !instance.state.isVisible; var isDestroyed = instance.state.isDestroyed; var isDisabled = !instance.state.isEnabled; var duration = getValueAtIndexOrReturn(instance.props.duration, 1, defaultProps.duration); if (isAlreadyHidden || isDestroyed || isDisabled) { return; } invokeHook('onHide', [instance], false); if (instance.props.onHide(instance) === false) { return; } instance.state.isVisible = false; instance.state.isShown = false; ignoreOnFirstUpdate = false; isVisibleFromClick = false; if (getIsDefaultRenderFn()) { popper.style.visibility = 'hidden'; } cleanupInteractiveMouseListeners(); removeDocumentPress(); handleStyles(true); if (getIsDefaultRenderFn()) { var _getDefaultTemplateCh4 = getDefaultTemplateChildren(), box = _getDefaultTemplateCh4.box, content = _getDefaultTemplateCh4.content; if (instance.props.animation) { setTransitionDuration([box, content], duration); setVisibilityState([box, content], 'hidden'); } } handleAriaContentAttribute(); handleAriaExpandedAttribute(); if (instance.props.animation) { if (getIsDefaultRenderFn()) { onTransitionedOut(duration, instance.unmount); } } else { instance.unmount(); } } function hideWithInteractivity(event) { /* istanbul ignore else */ { warnWhen(instance.state.isDestroyed, createMemoryLeakWarning('hideWithInteractivity')); } getDocument().addEventListener('mousemove', debouncedOnMouseMove); pushIfUnique(mouseMoveListeners, debouncedOnMouseMove); debouncedOnMouseMove(event); } function unmount() { /* istanbul ignore else */ { warnWhen(instance.state.isDestroyed, createMemoryLeakWarning('unmount')); } if (instance.state.isVisible) { instance.hide(); } if (!instance.state.isMounted) { return; } destroyPopperInstance(); // If a popper is not interactive, it will be appended outside the popper // tree by default. This seems mainly for interactive tippies, but we should // find a workaround if possible getNestedPopperTree().forEach(function (nestedPopper) { nestedPopper._tippy.unmount(); }); if (popper.parentNode) { popper.parentNode.removeChild(popper); } mountedInstances = mountedInstances.filter(function (i) { return i !== instance; }); instance.state.isMounted = false; invokeHook('onHidden', [instance]); } function destroy() { /* istanbul ignore else */ { warnWhen(instance.state.isDestroyed, createMemoryLeakWarning('destroy')); } if (instance.state.isDestroyed) { return; } instance.clearDelayTimeouts(); instance.unmount(); removeListeners(); delete reference._tippy; instance.state.isDestroyed = true; invokeHook('onDestroy', [instance]); } } function tippy(targets, optionalProps) { if (optionalProps === void 0) { optionalProps = {}; } var plugins = defaultProps.plugins.co