UNPKG

tippy.js

Version:

Highly customizable tooltip and popover library

704 lines (599 loc) 21.9 kB
/**! * tippy.js v5.2.1 * (c) 2017-2020 atomiks * MIT License */ import { e as errorWhen, _ as _extends, d as defaultProps, t as tippy, a as div, r as removeProperties, n as normalizeToArray, i as includes, s as setVisibilityState, w as warnWhen, B as BACKDROP_CLASS, g as getOwnerDocument, b as isMouseEvent, u as useIfDefined, c as currentInput, f as closestCallback, h as getBasePlacement, j as arrayFrom } from './tippy.chunk.esm.js'; export { l as createTippyWithPlugins, t as default, k as hideAll, R as roundArrow } from './tippy.chunk.esm.js'; import 'popper.js'; /** * Re-uses a single tippy element for many different tippy instances. * Replaces v4's `tippy.group()`. */ var createSingleton = function createSingleton(tippyInstances, optionalProps, /** @deprecated use Props.plugins */ plugins) { if (optionalProps === void 0) { optionalProps = {}; } if (plugins === void 0) { plugins = []; } if (process.env.NODE_ENV !== "production") { errorWhen(!Array.isArray(tippyInstances), ['The first argument passed to createSingleton() must be an array of tippy', 'instances. The passed value was', String(tippyInstances)].join(' ')); } plugins = optionalProps.plugins || plugins; tippyInstances.forEach(function (instance) { instance.disable(); }); var userAria = _extends({}, defaultProps, {}, optionalProps).aria; var currentAria; var currentTarget; var shouldSkipUpdate = false; var references = tippyInstances.map(function (instance) { return instance.reference; }); var singleton = { fn: function fn(instance) { function handleAriaDescribedByAttribute(isShow) { if (!currentAria) { return; } var attr = "aria-" + currentAria; if (isShow && !instance.props.interactive) { currentTarget.setAttribute(attr, instance.popperChildren.tooltip.id); } else { currentTarget.removeAttribute(attr); } } return { onAfterUpdate: function onAfterUpdate(_, _ref) { var aria = _ref.aria; // Ensure `aria` for the singleton instance stays `null`, while // changing the `userAria` value if (aria !== undefined && aria !== userAria) { if (!shouldSkipUpdate) { userAria = aria; } else { shouldSkipUpdate = true; instance.setProps({ aria: null }); shouldSkipUpdate = false; } } }, onDestroy: function onDestroy() { tippyInstances.forEach(function (instance) { instance.enable(); }); }, onMount: function onMount() { handleAriaDescribedByAttribute(true); }, onUntrigger: function onUntrigger() { handleAriaDescribedByAttribute(false); }, onTrigger: function onTrigger(_, event) { var target = event.currentTarget; var index = references.indexOf(target); // bail-out if (target === currentTarget) { return; } currentTarget = target; currentAria = userAria; if (instance.state.isVisible) { handleAriaDescribedByAttribute(true); } instance.popperInstance.reference = target; instance.setContent(tippyInstances[index].props.content); } }; } }; return tippy(div(), _extends({}, optionalProps, { plugins: [singleton].concat(plugins), aria: null, triggerTarget: references })); }; var BUBBLING_EVENTS_MAP = { mouseover: 'mouseenter', focusin: 'focus', click: 'click' }; /** * Creates a delegate instance that controls the creation of tippy instances * for child elements (`target` CSS selector). */ function delegate(targets, props, /** @deprecated use Props.plugins */ plugins) { if (plugins === void 0) { plugins = []; } if (process.env.NODE_ENV !== "production") { errorWhen(!(props && props.target), ['You must specity a `target` prop indicating a CSS selector string matching', 'the target elements that should receive a tippy.'].join(' ')); } plugins = props.plugins || plugins; var listeners = []; var childTippyInstances = []; var target = props.target; var nativeProps = removeProperties(props, ['target']); var parentProps = _extends({}, nativeProps, { plugins: plugins, trigger: 'manual' }); var childProps = _extends({}, nativeProps, { plugins: plugins, showOnCreate: true }); var returnValue = tippy(targets, parentProps); var normalizedReturnValue = normalizeToArray(returnValue); function onTrigger(event) { if (!event.target) { return; } var targetNode = event.target.closest(target); if (!targetNode) { return; } // Get relevant trigger with fallbacks: // 1. Check `data-tippy-trigger` attribute on target node // 2. Fallback to `trigger` passed to `delegate()` // 3. Fallback to `defaultProps.trigger` var trigger = targetNode.getAttribute('data-tippy-trigger') || props.trigger || defaultProps.trigger; // Only create the instance if the bubbling event matches the trigger type if (!includes(trigger, BUBBLING_EVENTS_MAP[event.type])) { return; } var instance = tippy(targetNode, childProps); if (instance) { childTippyInstances = childTippyInstances.concat(instance); } } function on(node, eventType, handler, options) { if (options === void 0) { options = false; } node.addEventListener(eventType, handler, options); listeners.push({ node: node, eventType: eventType, handler: handler, options: options }); } function addEventListeners(instance) { var reference = instance.reference; on(reference, 'mouseover', onTrigger); on(reference, 'focusin', onTrigger); on(reference, 'click', onTrigger); } function removeEventListeners() { listeners.forEach(function (_ref) { var node = _ref.node, eventType = _ref.eventType, handler = _ref.handler, options = _ref.options; node.removeEventListener(eventType, handler, options); }); listeners = []; } function applyMutations(instance) { var originalDestroy = instance.destroy; instance.destroy = function (shouldDestroyChildInstances) { if (shouldDestroyChildInstances === void 0) { shouldDestroyChildInstances = true; } if (shouldDestroyChildInstances) { childTippyInstances.forEach(function (instance) { instance.destroy(); }); } childTippyInstances = []; removeEventListeners(); originalDestroy(); }; addEventListeners(instance); } normalizedReturnValue.forEach(applyMutations); return returnValue; } var animateFill = { name: 'animateFill', defaultValue: false, fn: function fn(instance) { var _instance$popperChild = instance.popperChildren, tooltip = _instance$popperChild.tooltip, content = _instance$popperChild.content; var backdrop = instance.props.animateFill ? createBackdropElement() : null; function addBackdropToPopperChildren() { instance.popperChildren.backdrop = backdrop; } return { onCreate: function onCreate() { if (backdrop) { addBackdropToPopperChildren(); tooltip.insertBefore(backdrop, tooltip.firstElementChild); tooltip.setAttribute('data-animatefill', ''); tooltip.style.overflow = 'hidden'; instance.setProps({ animation: 'shift-away', arrow: false }); } }, onMount: function onMount() { if (backdrop) { var transitionDuration = tooltip.style.transitionDuration; var duration = Number(transitionDuration.replace('ms', '')); // The content should fade in after the backdrop has mostly filled the // tooltip element. `clip-path` is the other alternative but is not // well-supported and is buggy on some devices. content.style.transitionDelay = Math.round(duration / 10) + "ms"; backdrop.style.transitionDuration = transitionDuration; setVisibilityState([backdrop], 'visible'); // Warn if the stylesheets are not loaded if (process.env.NODE_ENV !== "production") { warnWhen(getComputedStyle(backdrop).position !== 'absolute', "The `tippy.js/dist/backdrop.css` stylesheet has not been\n imported!\n \n The `animateFill` plugin requires this stylesheet to work."); warnWhen(getComputedStyle(tooltip).transform === 'none', "The `tippy.js/animations/shift-away.css` stylesheet has not\n been imported!\n \n The `animateFill` plugin requires this stylesheet to work."); } } }, onShow: function onShow() { if (backdrop) { backdrop.style.transitionDuration = '0ms'; } }, onHide: function onHide() { if (backdrop) { setVisibilityState([backdrop], 'hidden'); } }, onAfterUpdate: function onAfterUpdate() { // With this type of prop, it's highly unlikely it will be changed // dynamically. We'll leave out the diff/update logic it to save bytes. // `popperChildren` is assigned a new object onAfterUpdate addBackdropToPopperChildren(); } }; } }; function createBackdropElement() { var backdrop = div(); backdrop.className = BACKDROP_CLASS; setVisibilityState([backdrop], 'hidden'); return backdrop; } var followCursor = { name: 'followCursor', defaultValue: false, fn: function fn(instance) { var reference = instance.reference, popper = instance.popper; var originalReference = null; // Support iframe contexts // Static check that assumes any of the `triggerTarget` or `reference` // nodes will never change documents, even when they are updated var doc = getOwnerDocument(instance.props.triggerTarget || reference); // Internal state var lastMouseMoveEvent; var mouseCoords = null; var isInternallySettingControlledProp = false; // These are controlled by this plugin, so we need to store the user's // original prop value var userProps = instance.props; function setUserProps(props) { var keys = Object.keys(props); keys.forEach(function (prop) { userProps[prop] = useIfDefined(props[prop], userProps[prop]); }); } function getIsManual() { return instance.props.trigger.trim() === 'manual'; } function getIsEnabled() { // #597 var isValidMouseEvent = getIsManual() ? true : // Check if a keyboard "click" mouseCoords !== null && !(mouseCoords.clientX === 0 && mouseCoords.clientY === 0); return instance.props.followCursor && isValidMouseEvent; } function getIsInitialBehavior() { return currentInput.isTouch || instance.props.followCursor === 'initial' && instance.state.isVisible; } function resetReference() { if (instance.popperInstance && originalReference) { instance.popperInstance.reference = originalReference; } } function handlePlacement() { // Due to `getVirtualOffsets()`, we need to reverse the placement if it's // shifted (start -> end, and vice-versa) // Early bail-out if (!getIsEnabled() && instance.props.placement === userProps.placement) { return; } var placement = userProps.placement; var shift = placement.split('-')[1]; isInternallySettingControlledProp = true; instance.setProps({ placement: getIsEnabled() && shift ? placement.replace(shift, shift === 'start' ? 'end' : 'start') : placement }); isInternallySettingControlledProp = false; } function handlePopperListeners() { if (!instance.popperInstance) { return; } // Popper's scroll listeners make sense for `true` only. TODO: work out // how to only listen horizontal scroll for "horizontal" and vertical // scroll for "vertical" if (getIsEnabled() && getIsInitialBehavior()) { instance.popperInstance.disableEventListeners(); } } function handleMouseMoveListener() { if (getIsEnabled()) { addListener(); } else { resetReference(); } } function triggerLastMouseMove() { if (getIsEnabled()) { onMouseMove(lastMouseMoveEvent); } } function addListener() { doc.addEventListener('mousemove', onMouseMove); } function removeListener() { doc.removeEventListener('mousemove', onMouseMove); } function onMouseMove(event) { var _lastMouseMoveEvent = lastMouseMoveEvent = event, clientX = _lastMouseMoveEvent.clientX, clientY = _lastMouseMoveEvent.clientY; if (!instance.popperInstance || !instance.state.currentPlacement) { 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 followCursor = instance.props.followCursor; var isHorizontal = followCursor === 'horizontal'; var isVertical = followCursor === 'vertical'; var isVerticalPlacement = includes(['top', 'bottom'], getBasePlacement(instance.state.currentPlacement)); // The virtual reference needs some size to prevent itself from overflowing var _getVirtualOffsets = getVirtualOffsets(popper, isVerticalPlacement), size = _getVirtualOffsets.size, x = _getVirtualOffsets.x, y = _getVirtualOffsets.y; if (isCursorOverReference || !instance.props.interactive) { // Preserve custom position ReferenceObjects, which may not be the // original targets reference passed as an argument if (originalReference === null) { originalReference = instance.popperInstance.reference; } instance.popperInstance.reference = { referenceNode: reference, // These `client` values don't get used by Popper.js if they are 0 clientWidth: 0, clientHeight: 0, getBoundingClientRect: function getBoundingClientRect() { var rect = reference.getBoundingClientRect(); return { width: isVerticalPlacement ? size : 0, height: isVerticalPlacement ? 0 : size, top: (isHorizontal ? rect.top : clientY) - y, bottom: (isHorizontal ? rect.bottom : clientY) + y, left: (isVertical ? rect.left : clientX) - x, right: (isVertical ? rect.right : clientX) + x }; } }; instance.popperInstance.update(); } if (getIsInitialBehavior()) { removeListener(); } } return { onAfterUpdate: function onAfterUpdate(_, partialProps) { if (!isInternallySettingControlledProp) { setUserProps(partialProps); if (partialProps.placement) { handlePlacement(); } } // A new placement causes the popperInstance to be recreated if (partialProps.placement) { handlePopperListeners(); } // Wait for `.update()` to set `instance.state.currentPlacement` to // the new placement requestAnimationFrame(triggerLastMouseMove); }, onMount: function onMount() { triggerLastMouseMove(); handlePopperListeners(); }, onShow: function onShow() { if (getIsManual()) { // Since there's no trigger event to use, we have to use these as // baseline coords mouseCoords = { clientX: 0, clientY: 0 }; // Ensure `lastMouseMoveEvent` doesn't access any other properties // of a MouseEvent here lastMouseMoveEvent = mouseCoords; handlePlacement(); handleMouseMoveListener(); } }, onTrigger: function onTrigger(_, event) { // Tapping on touch devices can trigger `mouseenter` then `focus` if (mouseCoords) { return; } if (isMouseEvent(event)) { mouseCoords = { clientX: event.clientX, clientY: event.clientY }; lastMouseMoveEvent = event; } handlePlacement(); handleMouseMoveListener(); }, onUntrigger: function onUntrigger() { // If untriggered before showing (`onHidden` will never be invoked) if (!instance.state.isVisible) { removeListener(); mouseCoords = null; } }, onHidden: function onHidden() { removeListener(); resetReference(); mouseCoords = null; } }; } }; function getVirtualOffsets(popper, isVerticalPlacement) { var size = isVerticalPlacement ? popper.offsetWidth : popper.offsetHeight; return { size: size, x: isVerticalPlacement ? size : 0, y: isVerticalPlacement ? 0 : size }; } // position. This will require the `followCursor` plugin's fixes for overflow // due to using event.clientX/Y values. (normalizedPlacement, getVirtualOffsets) var inlinePositioning = { name: 'inlinePositioning', defaultValue: false, fn: function fn(instance) { var reference = instance.reference; function getIsEnabled() { return !!instance.props.inlinePositioning; } return { onHidden: function onHidden() { if (getIsEnabled()) { instance.popperInstance.reference = reference; } }, onShow: function onShow() { if (!getIsEnabled()) { return; } instance.popperInstance.reference = { referenceNode: reference, // These `client` values don't get used by Popper.js if they are 0 clientWidth: 0, clientHeight: 0, getBoundingClientRect: function getBoundingClientRect() { return getInlineBoundingClientRect(instance.state.currentPlacement && getBasePlacement(instance.state.currentPlacement), reference.getBoundingClientRect(), arrayFrom(reference.getClientRects())); } }; } }; } }; function getInlineBoundingClientRect(currentBasePlacement, boundingRect, clientRects) { // Not an inline element, or placement is not yet known if (clientRects.length < 2 || currentBasePlacement === null) { return boundingRect; } switch (currentBasePlacement) { case 'top': case 'bottom': { var firstRect = clientRects[0]; var lastRect = clientRects[clientRects.length - 1]; var isTop = currentBasePlacement === 'top'; var top = firstRect.top; var bottom = lastRect.bottom; var left = isTop ? firstRect.left : lastRect.left; var right = isTop ? firstRect.right : lastRect.right; var width = right - left; var height = bottom - top; return { top: top, bottom: bottom, left: left, right: right, width: width, height: height }; } case 'left': case 'right': { var minLeft = Math.min.apply(Math, clientRects.map(function (rects) { return rects.left; })); var maxRight = Math.max.apply(Math, clientRects.map(function (rects) { return rects.right; })); var measureRects = clientRects.filter(function (rect) { return currentBasePlacement === 'left' ? rect.left === minLeft : rect.right === maxRight; }); var _top = measureRects[0].top; var _bottom = measureRects[measureRects.length - 1].bottom; var _left = minLeft; var _right = maxRight; var _width = _right - _left; var _height = _bottom - _top; return { top: _top, bottom: _bottom, left: _left, right: _right, width: _width, height: _height }; } default: { return boundingRect; } } } var sticky = { name: 'sticky', defaultValue: false, fn: function fn(instance) { var reference = instance.reference, popper = instance.popper; function getReference() { return instance.popperInstance ? instance.popperInstance.reference : reference; } function shouldCheck(value) { return instance.props.sticky === true || instance.props.sticky === value; } var prevRefRect = null; var prevPopRect = null; function updatePosition() { var currentRefRect = shouldCheck('reference') ? getReference().getBoundingClientRect() : null; var currentPopRect = shouldCheck('popper') ? popper.getBoundingClientRect() : null; if (currentRefRect && areRectsDifferent(prevRefRect, currentRefRect) || currentPopRect && areRectsDifferent(prevPopRect, currentPopRect)) { instance.popperInstance.update(); } prevRefRect = currentRefRect; prevPopRect = currentPopRect; if (instance.state.isMounted) { requestAnimationFrame(updatePosition); } } return { onMount: function onMount() { if (instance.props.sticky) { updatePosition(); } } }; } }; function areRectsDifferent(rectA, rectB) { if (rectA && rectB) { return rectA.top !== rectB.top || rectA.right !== rectB.right || rectA.bottom !== rectB.bottom || rectA.left !== rectB.left; } return true; } export { animateFill, createSingleton, delegate, followCursor, inlinePositioning, sticky }; //# sourceMappingURL=tippy.esm.js.map