601 lines
20 KiB
JavaScript
601 lines
20 KiB
JavaScript
/**!
|
||
* @fileOverview Kickass library to create and place poppers near their reference elements.
|
||
* @version 1.3.1
|
||
* @license
|
||
* Copyright (c) 2016 Federico Zivolo and contributors
|
||
*
|
||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||
* of this software and associated documentation files (the "Software"), to deal
|
||
* in the Software without restriction, including without limitation the rights
|
||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||
* copies of the Software, and to permit persons to whom the Software is
|
||
* furnished to do so, subject to the following conditions:
|
||
*
|
||
* The above copyright notice and this permission notice shall be included in all
|
||
* copies or substantial portions of the Software.
|
||
*
|
||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||
* SOFTWARE.
|
||
*/
|
||
(function (global, factory) {
|
||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./popper')) :
|
||
typeof define === 'function' && define.amd ? define(['./popper'], factory) :
|
||
(global.Tooltip = factory(global.Popper));
|
||
}(this, (function (Popper) { 'use strict';
|
||
|
||
Popper = Popper && Popper.hasOwnProperty('default') ? Popper['default'] : Popper;
|
||
|
||
/**
|
||
* Check if the given variable is a function
|
||
* @method
|
||
* @memberof Popper.Utils
|
||
* @argument {Any} functionToCheck - variable to check
|
||
* @returns {Boolean} answer to: is a function?
|
||
*/
|
||
function isFunction(functionToCheck) {
|
||
var getType = {};
|
||
return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
|
||
}
|
||
|
||
var classCallCheck = function (instance, Constructor) {
|
||
if (!(instance instanceof Constructor)) {
|
||
throw new TypeError("Cannot call a class as a function");
|
||
}
|
||
};
|
||
|
||
var createClass = function () {
|
||
function defineProperties(target, props) {
|
||
for (var i = 0; i < props.length; i++) {
|
||
var descriptor = props[i];
|
||
descriptor.enumerable = descriptor.enumerable || false;
|
||
descriptor.configurable = true;
|
||
if ("value" in descriptor) descriptor.writable = true;
|
||
Object.defineProperty(target, descriptor.key, descriptor);
|
||
}
|
||
}
|
||
|
||
return function (Constructor, protoProps, staticProps) {
|
||
if (protoProps) defineProperties(Constructor.prototype, protoProps);
|
||
if (staticProps) defineProperties(Constructor, staticProps);
|
||
return Constructor;
|
||
};
|
||
}();
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
var _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;
|
||
};
|
||
|
||
var DEFAULT_OPTIONS = {
|
||
container: false,
|
||
delay: 0,
|
||
html: false,
|
||
placement: 'top',
|
||
title: '',
|
||
template: '<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',
|
||
trigger: 'hover focus',
|
||
offset: 0,
|
||
arrowSelector: '.tooltip-arrow, .tooltip__arrow',
|
||
innerSelector: '.tooltip-inner, .tooltip__inner'
|
||
};
|
||
|
||
var Tooltip = function () {
|
||
/**
|
||
* Create a new Tooltip.js instance
|
||
* @class Tooltip
|
||
* @param {HTMLElement} reference - The DOM node used as reference of the tooltip (it can be a jQuery element).
|
||
* @param {Object} options
|
||
* @param {String} options.placement='top'
|
||
* Placement of the popper accepted values: `top(-start, -end), right(-start, -end), bottom(-start, -end),
|
||
* left(-start, -end)`
|
||
* @param {String} options.arrowSelector='.tooltip-arrow, .tooltip__arrow' - className used to locate the DOM arrow element in the tooltip.
|
||
* @param {String} options.innerSelector='.tooltip-inner, .tooltip__inner' - className used to locate the DOM inner element in the tooltip.
|
||
* @param {HTMLElement|String|false} options.container=false - Append the tooltip to a specific element.
|
||
* @param {Number|Object} options.delay=0
|
||
* Delay showing and hiding the tooltip (ms) - does not apply to manual trigger type.
|
||
* If a number is supplied, delay is applied to both hide/show.
|
||
* Object structure is: `{ show: 500, hide: 100 }`
|
||
* @param {Boolean} options.html=false - Insert HTML into the tooltip. If false, the content will inserted with `textContent`.
|
||
* @param {String} [options.template='<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>']
|
||
* Base HTML to used when creating the tooltip.
|
||
* The tooltip's `title` will be injected into the `.tooltip-inner` or `.tooltip__inner`.
|
||
* `.tooltip-arrow` or `.tooltip__arrow` will become the tooltip's arrow.
|
||
* The outermost wrapper element should have the `.tooltip` class.
|
||
* @param {String|HTMLElement|TitleFunction} options.title='' - Default title value if `title` attribute isn't present.
|
||
* @param {String} [options.trigger='hover focus']
|
||
* How tooltip is triggered - click, hover, focus, manual.
|
||
* You may pass multiple triggers; separate them with a space. `manual` cannot be combined with any other trigger.
|
||
* @param {Boolean} options.closeOnClickOutside=false - Close a popper on click outside of the popper and reference element. This has effect only when options.trigger is 'click'.
|
||
* @param {String|HTMLElement} options.boundariesElement
|
||
* The element used as boundaries for the tooltip. For more information refer to Popper.js'
|
||
* [boundariesElement docs](https://popper.js.org/popper-documentation.html)
|
||
* @param {Number|String} options.offset=0 - Offset of the tooltip relative to its reference. For more information refer to Popper.js'
|
||
* [offset docs](https://popper.js.org/popper-documentation.html)
|
||
* @param {Object} options.popperOptions={} - Popper options, will be passed directly to popper instance. For more information refer to Popper.js'
|
||
* [options docs](https://popper.js.org/popper-documentation.html)
|
||
* @return {Object} instance - The generated tooltip instance
|
||
*/
|
||
function Tooltip(reference, options) {
|
||
classCallCheck(this, Tooltip);
|
||
|
||
_initialiseProps.call(this);
|
||
|
||
// apply user options over default ones
|
||
options = _extends({}, DEFAULT_OPTIONS, options);
|
||
|
||
reference.jquery && (reference = reference[0]);
|
||
|
||
// cache reference and options
|
||
this.reference = reference;
|
||
this.options = options;
|
||
|
||
// get events list
|
||
var events = typeof options.trigger === 'string' ? options.trigger.split(' ').filter(function (trigger) {
|
||
return ['click', 'hover', 'focus'].indexOf(trigger) !== -1;
|
||
}) : [];
|
||
|
||
// set initial state
|
||
this._isOpen = false;
|
||
this._popperOptions = {};
|
||
|
||
// set event listeners
|
||
this._setEventListeners(reference, events, options);
|
||
}
|
||
|
||
//
|
||
// Public methods
|
||
//
|
||
|
||
/**
|
||
* Reveals an element's tooltip. This is considered a "manual" triggering of the tooltip.
|
||
* Tooltips with zero-length titles are never displayed.
|
||
* @method Tooltip#show
|
||
* @memberof Tooltip
|
||
*/
|
||
|
||
|
||
/**
|
||
* Hides an element’s tooltip. This is considered a “manual” triggering of the tooltip.
|
||
* @method Tooltip#hide
|
||
* @memberof Tooltip
|
||
*/
|
||
|
||
|
||
/**
|
||
* Hides and destroys an element’s tooltip.
|
||
* @method Tooltip#dispose
|
||
* @memberof Tooltip
|
||
*/
|
||
|
||
|
||
/**
|
||
* Toggles an element’s tooltip. This is considered a “manual” triggering of the tooltip.
|
||
* @method Tooltip#toggle
|
||
* @memberof Tooltip
|
||
*/
|
||
|
||
|
||
/**
|
||
* Updates the tooltip's title content
|
||
* @method Tooltip#updateTitleContent
|
||
* @memberof Tooltip
|
||
* @param {String|HTMLElement} title - The new content to use for the title
|
||
*/
|
||
|
||
|
||
//
|
||
// Private methods
|
||
//
|
||
|
||
createClass(Tooltip, [{
|
||
key: '_create',
|
||
|
||
|
||
/**
|
||
* Creates a new tooltip node
|
||
* @memberof Tooltip
|
||
* @private
|
||
* @param {HTMLElement} reference
|
||
* @param {String} template
|
||
* @param {String|HTMLElement|TitleFunction} title
|
||
* @param {Boolean} allowHtml
|
||
* @return {HTMLElement} tooltipNode
|
||
*/
|
||
value: function _create(reference, template, title, allowHtml) {
|
||
// create tooltip element
|
||
var tooltipGenerator = window.document.createElement('div');
|
||
tooltipGenerator.innerHTML = template.trim();
|
||
var tooltipNode = tooltipGenerator.childNodes[0];
|
||
|
||
// add unique ID to our tooltip (needed for accessibility reasons)
|
||
tooltipNode.id = 'tooltip_' + Math.random().toString(36).substr(2, 10);
|
||
|
||
// set initial `aria-hidden` state to `false` (it's visible!)
|
||
tooltipNode.setAttribute('aria-hidden', 'false');
|
||
|
||
// add title to tooltip
|
||
var titleNode = tooltipGenerator.querySelector(this.options.innerSelector);
|
||
this._addTitleContent(reference, title, allowHtml, titleNode);
|
||
|
||
// return the generated tooltip node
|
||
return tooltipNode;
|
||
}
|
||
}, {
|
||
key: '_addTitleContent',
|
||
value: function _addTitleContent(reference, title, allowHtml, titleNode) {
|
||
if (title.nodeType === 1 || title.nodeType === 11) {
|
||
// if title is a element node or document fragment, append it only if allowHtml is true
|
||
allowHtml && titleNode.appendChild(title);
|
||
} else if (isFunction(title)) {
|
||
// if title is a function, call it and set textContent or innerHtml depending by `allowHtml` value
|
||
var titleText = title.call(reference);
|
||
allowHtml ? titleNode.innerHTML = titleText : titleNode.textContent = titleText;
|
||
} else {
|
||
// if it's just a simple text, set textContent or innerHtml depending by `allowHtml` value
|
||
allowHtml ? titleNode.innerHTML = title : titleNode.textContent = title;
|
||
}
|
||
}
|
||
}, {
|
||
key: '_show',
|
||
value: function _show(reference, options) {
|
||
// don't show if it's already visible
|
||
// or if it's not being showed
|
||
if (this._isOpen && !this._isOpening) {
|
||
return this;
|
||
}
|
||
this._isOpen = true;
|
||
|
||
// if the tooltipNode already exists, just show it
|
||
if (this._tooltipNode) {
|
||
this._tooltipNode.style.visibility = 'visible';
|
||
this._tooltipNode.setAttribute('aria-hidden', 'false');
|
||
this.popperInstance.update();
|
||
return this;
|
||
}
|
||
|
||
// get title
|
||
var title = reference.getAttribute('title') || options.title;
|
||
|
||
// don't show tooltip if no title is defined
|
||
if (!title) {
|
||
return this;
|
||
}
|
||
|
||
// create tooltip node
|
||
var tooltipNode = this._create(reference, options.template, title, options.html);
|
||
|
||
// Add `aria-describedby` to our reference element for accessibility reasons
|
||
reference.setAttribute('aria-describedby', tooltipNode.id);
|
||
|
||
// append tooltip to container
|
||
var container = this._findContainer(options.container, reference);
|
||
|
||
this._append(tooltipNode, container);
|
||
|
||
this._popperOptions = _extends({}, options.popperOptions, {
|
||
placement: options.placement
|
||
});
|
||
|
||
this._popperOptions.modifiers = _extends({}, this._popperOptions.modifiers, {
|
||
arrow: {
|
||
element: this.options.arrowSelector
|
||
},
|
||
offset: {
|
||
offset: options.offset
|
||
}
|
||
});
|
||
|
||
if (options.boundariesElement) {
|
||
this._popperOptions.modifiers.preventOverflow = {
|
||
boundariesElement: options.boundariesElement
|
||
};
|
||
}
|
||
|
||
this.popperInstance = new Popper(reference, tooltipNode, this._popperOptions);
|
||
|
||
this._tooltipNode = tooltipNode;
|
||
|
||
return this;
|
||
}
|
||
}, {
|
||
key: '_hide',
|
||
value: function _hide() /*reference, options*/{
|
||
// don't hide if it's already hidden
|
||
if (!this._isOpen) {
|
||
return this;
|
||
}
|
||
|
||
this._isOpen = false;
|
||
|
||
// hide tooltipNode
|
||
this._tooltipNode.style.visibility = 'hidden';
|
||
this._tooltipNode.setAttribute('aria-hidden', 'true');
|
||
|
||
return this;
|
||
}
|
||
}, {
|
||
key: '_dispose',
|
||
value: function _dispose() {
|
||
var _this = this;
|
||
|
||
// remove event listeners first to prevent any unexpected behaviour
|
||
this._events.forEach(function (_ref) {
|
||
var func = _ref.func,
|
||
event = _ref.event;
|
||
|
||
_this.reference.removeEventListener(event, func);
|
||
});
|
||
this._events = [];
|
||
|
||
if (this._tooltipNode) {
|
||
this._hide();
|
||
|
||
// destroy instance
|
||
this.popperInstance.destroy();
|
||
|
||
// destroy tooltipNode if removeOnDestroy is not set, as popperInstance.destroy() already removes the element
|
||
if (!this.popperInstance.options.removeOnDestroy) {
|
||
this._tooltipNode.parentNode.removeChild(this._tooltipNode);
|
||
this._tooltipNode = null;
|
||
}
|
||
}
|
||
return this;
|
||
}
|
||
}, {
|
||
key: '_findContainer',
|
||
value: function _findContainer(container, reference) {
|
||
// if container is a query, get the relative element
|
||
if (typeof container === 'string') {
|
||
container = window.document.querySelector(container);
|
||
} else if (container === false) {
|
||
// if container is `false`, set it to reference parent
|
||
container = reference.parentNode;
|
||
}
|
||
return container;
|
||
}
|
||
|
||
/**
|
||
* Append tooltip to container
|
||
* makes popper instnce to redraw itself in order to fix word wrap on dynamic content
|
||
* @memberof Tooltip
|
||
* @private
|
||
* @param {HTMLElement} tooltipNode
|
||
* @param {HTMLElement|String|false} container
|
||
*/
|
||
|
||
}, {
|
||
key: '_append',
|
||
value: function _append(tooltipNode, container) {
|
||
var _this2 = this;
|
||
|
||
container.appendChild(tooltipNode);
|
||
|
||
setTimeout(function () {
|
||
if (_this2._isOpen) {
|
||
_this2.hide();
|
||
}
|
||
_this2.show();
|
||
});
|
||
}
|
||
}, {
|
||
key: '_setEventListeners',
|
||
value: function _setEventListeners(reference, events, options) {
|
||
var _this3 = this;
|
||
|
||
var directEvents = [];
|
||
var oppositeEvents = [];
|
||
|
||
events.forEach(function (event) {
|
||
switch (event) {
|
||
case 'hover':
|
||
directEvents.push('mouseenter');
|
||
oppositeEvents.push('mouseleave');
|
||
break;
|
||
case 'focus':
|
||
directEvents.push('focus');
|
||
oppositeEvents.push('blur');
|
||
break;
|
||
case 'click':
|
||
directEvents.push('click');
|
||
oppositeEvents.push('click');
|
||
break;
|
||
}
|
||
});
|
||
|
||
// schedule show tooltip
|
||
directEvents.forEach(function (event) {
|
||
var func = function func(evt) {
|
||
if (_this3._isOpening === true) {
|
||
return;
|
||
}
|
||
evt.usedByTooltip = true;
|
||
_this3._scheduleShow(reference, options.delay, options, evt);
|
||
};
|
||
_this3._events.push({ event: event, func: func });
|
||
reference.addEventListener(event, func);
|
||
});
|
||
|
||
// schedule hide tooltip
|
||
oppositeEvents.forEach(function (event) {
|
||
var func = function func(evt) {
|
||
if (evt.usedByTooltip === true) {
|
||
return;
|
||
}
|
||
_this3._scheduleHide(reference, options.delay, options, evt);
|
||
};
|
||
_this3._events.push({ event: event, func: func });
|
||
reference.addEventListener(event, func);
|
||
if (event === 'click' && options.closeOnClickOutside) {
|
||
document.addEventListener('mousedown', function (e) {
|
||
if (!_this3._isOpening) {
|
||
return;
|
||
}
|
||
var popper = _this3.popperInstance.popper;
|
||
if (reference.contains(e.target) || popper.contains(e.target)) {
|
||
return;
|
||
}
|
||
func(e);
|
||
}, true);
|
||
}
|
||
});
|
||
}
|
||
}, {
|
||
key: '_scheduleShow',
|
||
value: function _scheduleShow(reference, delay, options /*, evt */) {
|
||
var _this4 = this;
|
||
|
||
this._isOpening = true;
|
||
// defaults to 0
|
||
var computedDelay = delay && delay.show || delay || 0;
|
||
this._showTimeout = window.setTimeout(function () {
|
||
return _this4._show(reference, options);
|
||
}, computedDelay);
|
||
}
|
||
}, {
|
||
key: '_scheduleHide',
|
||
value: function _scheduleHide(reference, delay, options, evt) {
|
||
var _this5 = this;
|
||
|
||
this._isOpening = false;
|
||
// defaults to 0
|
||
var computedDelay = delay && delay.hide || delay || 0;
|
||
window.setTimeout(function () {
|
||
window.clearTimeout(_this5._showTimeout);
|
||
if (_this5._isOpen === false) {
|
||
return;
|
||
}
|
||
if (!document.body.contains(_this5._tooltipNode)) {
|
||
return;
|
||
}
|
||
|
||
// if we are hiding because of a mouseleave, we must check that the new
|
||
// reference isn't the tooltip, because in this case we don't want to hide it
|
||
if (evt.type === 'mouseleave') {
|
||
var isSet = _this5._setTooltipNodeEvent(evt, reference, delay, options);
|
||
|
||
// if we set the new event, don't hide the tooltip yet
|
||
// the new event will take care to hide it if necessary
|
||
if (isSet) {
|
||
return;
|
||
}
|
||
}
|
||
|
||
_this5._hide(reference, options);
|
||
}, computedDelay);
|
||
}
|
||
}, {
|
||
key: '_updateTitleContent',
|
||
value: function _updateTitleContent(title) {
|
||
if (typeof this._tooltipNode === 'undefined') {
|
||
if (typeof this.options.title !== 'undefined') {
|
||
this.options.title = title;
|
||
}
|
||
return;
|
||
}
|
||
var titleNode = this._tooltipNode.querySelector(this.options.innerSelector);
|
||
this._clearTitleContent(titleNode, this.options.html, this.reference.getAttribute('title') || this.options.title);
|
||
this._addTitleContent(this.reference, title, this.options.html, titleNode);
|
||
this.options.title = title;
|
||
this.popperInstance.update();
|
||
}
|
||
}, {
|
||
key: '_clearTitleContent',
|
||
value: function _clearTitleContent(titleNode, allowHtml, lastTitle) {
|
||
if (lastTitle.nodeType === 1 || lastTitle.nodeType === 11) {
|
||
allowHtml && titleNode.removeChild(lastTitle);
|
||
} else {
|
||
allowHtml ? titleNode.innerHTML = '' : titleNode.textContent = '';
|
||
}
|
||
}
|
||
}]);
|
||
return Tooltip;
|
||
}();
|
||
|
||
/**
|
||
* Title function, its context is the Tooltip instance.
|
||
* @memberof Tooltip
|
||
* @callback TitleFunction
|
||
* @return {String} placement - The desired title.
|
||
*/
|
||
|
||
|
||
var _initialiseProps = function _initialiseProps() {
|
||
var _this6 = this;
|
||
|
||
this.show = function () {
|
||
return _this6._show(_this6.reference, _this6.options);
|
||
};
|
||
|
||
this.hide = function () {
|
||
return _this6._hide();
|
||
};
|
||
|
||
this.dispose = function () {
|
||
return _this6._dispose();
|
||
};
|
||
|
||
this.toggle = function () {
|
||
if (_this6._isOpen) {
|
||
return _this6.hide();
|
||
} else {
|
||
return _this6.show();
|
||
}
|
||
};
|
||
|
||
this.updateTitleContent = function (title) {
|
||
return _this6._updateTitleContent(title);
|
||
};
|
||
|
||
this._events = [];
|
||
|
||
this._setTooltipNodeEvent = function (evt, reference, delay, options) {
|
||
var relatedreference = evt.relatedreference || evt.toElement || evt.relatedTarget;
|
||
|
||
var callback = function callback(evt2) {
|
||
var relatedreference2 = evt2.relatedreference || evt2.toElement || evt2.relatedTarget;
|
||
|
||
// Remove event listener after call
|
||
_this6._tooltipNode.removeEventListener(evt.type, callback);
|
||
|
||
// If the new reference is not the reference element
|
||
if (!reference.contains(relatedreference2)) {
|
||
// Schedule to hide tooltip
|
||
_this6._scheduleHide(reference, options.delay, options, evt2);
|
||
}
|
||
};
|
||
|
||
if (_this6._tooltipNode.contains(relatedreference)) {
|
||
// listen to mouseleave on the tooltip element to be able to hide the tooltip
|
||
_this6._tooltipNode.addEventListener(evt.type, callback);
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
};
|
||
};
|
||
|
||
return Tooltip;
|
||
|
||
})));
|
||
//# sourceMappingURL=tooltip.js.map
|