@atlassian/aui
Version:
Atlassian User Interface library
227 lines (178 loc) • 6.54 kB
JavaScript
import $ from './jquery';
import { createPopper } from '@popperjs/core';
const AUI_TOOLTIP_CLASS_NAME = 'aui-tooltip';
const AUI_TOOLTIP_ID = 'aui-tooltip';
/**
* The purpose of this map is to make it possible to use old Tipsy tooltip positions
* with Popper.
*
* @enum
* @name GravityOptions
* @type {{n: string, ne: string, e: string, se: string, s: string, sw: string, w: string, nw: string}}
*/
const GRAVITY_MAP = {
'n': 'bottom',
'ne': 'bottom-end',
'e': 'left',
'se': 'top-end',
's': 'top',
'sw': 'top-start',
'w': 'right',
'nw': 'bottom-start',
};
// This key is used to differentiate events related to this particular plugin.
const pluginKey = 'aui-tooltip';
const defaultOptions = {
gravity: 'n',
html: false,
live: false,
suppress: () => false
}
let $sharedTip;
class Tooltip {
constructor(triggerElement, options) {
this.triggerElement = triggerElement;
this.$triggerElement = $(this.triggerElement);
this.options = { ...defaultOptions, ...options };
this.moveTitleToTooltip()
}
destroy() {
this.unbindHandlers();
this.hide();
tooltipsByDomNode.delete(this.triggerElement);
}
moveTitleToTooltip() {
const tooltip = this;
const $triggerElement = this.$triggerElement;
$triggerElement.attr('title', function (_, originalTitle) {
tooltip.originalTitle = originalTitle;
$triggerElement.attr('aria-describedby', AUI_TOOLTIP_ID);
return null;
});
}
unbindHandlers() {
const selector = this.options.live;
// Keep in mind that unbinding handlers from one tooltip
// managed by delegation will unbind handlers for the whole
// collection.
if (this.options.$delegationRoot && selector) {
this.options.$delegationRoot.off(`.${pluginKey}`, selector);
return;
}
// We only need to unbind event handlers from this particular element
this.$triggerElement.off(`.${pluginKey}`);
}
getFloatingTip() {
const options = this.options;
let title = typeof options.title === 'function' ?
options.title :
typeof options.title === 'string' ?
() => options.title :
() => this.originalTitle || '';
if ($sharedTip === undefined) {
$sharedTip =
$(`<div id="${AUI_TOOLTIP_ID}" class="${AUI_TOOLTIP_CLASS_NAME} assistive" role="tooltip"><p class="aui-tooltip-content"></p></div>`)
$(document.body).append($sharedTip);
}
if (options.html) {
$sharedTip.find('.aui-tooltip-content').html(title.call(this.triggerElement));
} else {
$sharedTip.find('.aui-tooltip-content').text(title.call(this.triggerElement));
}
return $sharedTip;
}
show() {
const triggerElement = this.triggerElement;
const $tip = this.getFloatingTip();
const placement = GRAVITY_MAP[this.options.gravity];
clearTimeout(this.popperTimeout);
if (typeof this.options.suppress === 'function') {
if (this.options.suppress.call(triggerElement) === true) {
return;
}
}
this.popperTimeout = setTimeout(() => {
const tipNode = $tip.get(0);
// tipNode.setAttribute('data-show', '');
tipNode.classList.remove('assistive');
this.popperInstance = createPopper(triggerElement, tipNode, {
placement,
modifiers: [
{
name: 'offset',
options: {
offset: [0, 4],
},
},
],
});
$(window).on(`scroll.${pluginKey}`, () => this.hide());
}, 500);
}
hide() {
const tipNode = this.getFloatingTip().get(0);
// tipNode.removeAttribute('data-show');
tipNode.classList.add('assistive');
clearTimeout(this.popperTimeout);
if (this.popperInstance) {
this.popperInstance.destroy();
delete this.popperInstance;
}
$(window).off(`scroll.${pluginKey}`);
}
}
const tooltipsByDomNode = new WeakMap();
const getTooltipInstance = (domNode, options) => {
// Options will be ignored if there is an existing tooltip instance
// assigned to given DOM node. To override it you need to first destroy
// the old tooltip.
let tooltip = tooltipsByDomNode.get(domNode);
if (tooltip === undefined) {
tooltip = new Tooltip(domNode, options);
tooltipsByDomNode.set(domNode, tooltip);
}
return tooltip;
}
const namespacify = events => events.map(event => `${event}.${pluginKey}`).join(' ');
const activationEvents = namespacify(['mouseenter', 'focus']);
const deactivationEvents = namespacify(['click', 'mouseleave', 'blur']);
$.fn.tooltip = function (arg) {
// We have an actual jQuery collection available under `this`
const $collection = this;
// Get the tooltip instance assigned to the first element in the collection
if (arg === true) {
const firstDomNode = $collection.get(0)
return getTooltipInstance(firstDomNode);
}
// Call the particular method from the first tooltip instance
if (typeof arg === 'string') {
const tooltip = $collection.tooltip(true);
const commandName = arg;
if (typeof tooltip[commandName] !== 'function') {
throw new Error(`Method ${commandName} does not exist on tooltip.`)
}
tooltip[commandName]();
return $collection;
}
const options = arg || {}
const show = function () {
const tooltip = getTooltipInstance(this, options);
tooltip.show();
}
const hide = function () {
const tooltip = getTooltipInstance(this, options);
tooltip.hide();
}
const selector = options.live;
if (selector !== undefined) {
// We store it so that it's possible to kill the whole delegation later
options.$delegationRoot = $collection;
$collection.on(activationEvents, selector, show);
$collection.on(deactivationEvents, selector, hide);
return $collection;
}
$collection.on(activationEvents, show);
$collection.on(deactivationEvents, hide);
return $collection;
};
export default $;