tippy.js
Version:
Highly customizable tooltip and popover library
704 lines (599 loc) • 21.9 kB
JavaScript
/**!
* 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