tooltip.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539
  1. /**!
  2. * @fileOverview Kickass library to create and place poppers near their reference elements.
  3. * @version 1.1.5
  4. * @license
  5. * Copyright (c) 2016 Federico Zivolo and contributors
  6. *
  7. * Permission is hereby granted, free of charge, to any person obtaining a copy
  8. * of this software and associated documentation files (the "Software"), to deal
  9. * in the Software without restriction, including without limitation the rights
  10. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11. * copies of the Software, and to permit persons to whom the Software is
  12. * furnished to do so, subject to the following conditions:
  13. *
  14. * The above copyright notice and this permission notice shall be included in all
  15. * copies or substantial portions of the Software.
  16. *
  17. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  22. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  23. * SOFTWARE.
  24. */
  25. (function (global, factory) {
  26. typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('popper.js')) :
  27. typeof define === 'function' && define.amd ? define(['popper.js'], factory) :
  28. (global.Tooltip = factory(global.Popper));
  29. }(this, (function (Popper) { 'use strict';
  30. Popper = Popper && 'default' in Popper ? Popper['default'] : Popper;
  31. /**
  32. * Check if the given variable is a function
  33. * @method
  34. * @memberof Popper.Utils
  35. * @argument {Any} functionToCheck - variable to check
  36. * @returns {Boolean} answer to: is a function?
  37. */
  38. function isFunction(functionToCheck) {
  39. var getType = {};
  40. return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
  41. }
  42. var classCallCheck = function (instance, Constructor) {
  43. if (!(instance instanceof Constructor)) {
  44. throw new TypeError("Cannot call a class as a function");
  45. }
  46. };
  47. var createClass = function () {
  48. function defineProperties(target, props) {
  49. for (var i = 0; i < props.length; i++) {
  50. var descriptor = props[i];
  51. descriptor.enumerable = descriptor.enumerable || false;
  52. descriptor.configurable = true;
  53. if ("value" in descriptor) descriptor.writable = true;
  54. Object.defineProperty(target, descriptor.key, descriptor);
  55. }
  56. }
  57. return function (Constructor, protoProps, staticProps) {
  58. if (protoProps) defineProperties(Constructor.prototype, protoProps);
  59. if (staticProps) defineProperties(Constructor, staticProps);
  60. return Constructor;
  61. };
  62. }();
  63. var _extends = Object.assign || function (target) {
  64. for (var i = 1; i < arguments.length; i++) {
  65. var source = arguments[i];
  66. for (var key in source) {
  67. if (Object.prototype.hasOwnProperty.call(source, key)) {
  68. target[key] = source[key];
  69. }
  70. }
  71. }
  72. return target;
  73. };
  74. var DEFAULT_OPTIONS = {
  75. container: false,
  76. delay: 0,
  77. html: false,
  78. placement: 'top',
  79. title: '',
  80. template: '<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',
  81. trigger: 'hover focus',
  82. offset: 0
  83. };
  84. var Tooltip = function () {
  85. /**
  86. * Create a new Tooltip.js instance
  87. * @class Tooltip
  88. * @param {HTMLElement} reference - The DOM node used as reference of the tooltip (it can be a jQuery element).
  89. * @param {Object} options
  90. * @param {String} options.placement=bottom
  91. * Placement of the popper accepted values: `top(-start, -end), right(-start, -end), bottom(-start, -end),
  92. * left(-start, -end)`
  93. * @param {HTMLElement|String|false} options.container=false - Append the tooltip to a specific element.
  94. * @param {Number|Object} options.delay=0
  95. * Delay showing and hiding the tooltip (ms) - does not apply to manual trigger type.
  96. * If a number is supplied, delay is applied to both hide/show.
  97. * Object structure is: `{ show: 500, hide: 100 }`
  98. * @param {Boolean} options.html=false - Insert HTML into the tooltip. If false, the content will inserted with `innerText`.
  99. * @param {String|PlacementFunction} options.placement='top' - One of the allowed placements, or a function returning one of them.
  100. * @param {String} [options.template='<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>']
  101. * Base HTML to used when creating the tooltip.
  102. * The tooltip's `title` will be injected into the `.tooltip-inner` or `.tooltip__inner`.
  103. * `.tooltip-arrow` or `.tooltip__arrow` will become the tooltip's arrow.
  104. * The outermost wrapper element should have the `.tooltip` class.
  105. * @param {String|HTMLElement|TitleFunction} options.title='' - Default title value if `title` attribute isn't present.
  106. * @param {String} [options.trigger='hover focus']
  107. * How tooltip is triggered - click, hover, focus, manual.
  108. * You may pass multiple triggers; separate them with a space. `manual` cannot be combined with any other trigger.
  109. * @param {HTMLElement} options.boundariesElement
  110. * The element used as boundaries for the tooltip. For more information refer to Popper.js'
  111. * [boundariesElement docs](https://popper.js.org/popper-documentation.html)
  112. * @param {Number|String} options.offset=0 - Offset of the tooltip relative to its reference. For more information refer to Popper.js'
  113. * [offset docs](https://popper.js.org/popper-documentation.html)
  114. * @param {Object} options.popperOptions={} - Popper options, will be passed directly to popper instance. For more information refer to Popper.js'
  115. * [options docs](https://popper.js.org/popper-documentation.html)
  116. * @return {Object} instance - The generated tooltip instance
  117. */
  118. function Tooltip(reference, options) {
  119. classCallCheck(this, Tooltip);
  120. _initialiseProps.call(this);
  121. // apply user options over default ones
  122. options = _extends({}, DEFAULT_OPTIONS, options);
  123. reference.jquery && (reference = reference[0]);
  124. // cache reference and options
  125. this.reference = reference;
  126. this.options = options;
  127. // get events list
  128. var events = typeof options.trigger === 'string' ? options.trigger.split(' ').filter(function (trigger) {
  129. return ['click', 'hover', 'focus'].indexOf(trigger) !== -1;
  130. }) : [];
  131. // set initial state
  132. this._isOpen = false;
  133. // set event listeners
  134. this._setEventListeners(reference, events, options);
  135. }
  136. //
  137. // Public methods
  138. //
  139. /**
  140. * Reveals an element's tooltip. This is considered a "manual" triggering of the tooltip.
  141. * Tooltips with zero-length titles are never displayed.
  142. * @method Tooltip#show
  143. * @memberof Tooltip
  144. */
  145. /**
  146. * Hides an element’s tooltip. This is considered a “manual” triggering of the tooltip.
  147. * @method Tooltip#hide
  148. * @memberof Tooltip
  149. */
  150. /**
  151. * Hides and destroys an element’s tooltip.
  152. * @method Tooltip#dispose
  153. * @memberof Tooltip
  154. */
  155. /**
  156. * Toggles an element’s tooltip. This is considered a “manual” triggering of the tooltip.
  157. * @method Tooltip#toggle
  158. * @memberof Tooltip
  159. */
  160. //
  161. // Defaults
  162. //
  163. //
  164. // Private methods
  165. //
  166. createClass(Tooltip, [{
  167. key: '_create',
  168. /**
  169. * Creates a new tooltip node
  170. * @memberof Tooltip
  171. * @private
  172. * @param {HTMLElement} reference
  173. * @param {String} template
  174. * @param {String|HTMLElement|TitleFunction} title
  175. * @param {Boolean} allowHtml
  176. * @return {HTMLelement} tooltipNode
  177. */
  178. value: function _create(reference, template, title, allowHtml) {
  179. // create tooltip element
  180. var tooltipGenerator = window.document.createElement('div');
  181. tooltipGenerator.innerHTML = template.trim();
  182. var tooltipNode = tooltipGenerator.childNodes[0];
  183. // add unique ID to our tooltip (needed for accessibility reasons)
  184. tooltipNode.id = 'tooltip_' + Math.random().toString(36).substr(2, 10);
  185. // set initial `aria-hidden` state to `false` (it's visible!)
  186. tooltipNode.setAttribute('aria-hidden', 'false');
  187. // add title to tooltip
  188. var titleNode = tooltipGenerator.querySelector(this.innerSelector);
  189. if (title.nodeType === 1) {
  190. // if title is a node, append it only if allowHtml is true
  191. allowHtml && titleNode.appendChild(title);
  192. } else if (isFunction(title)) {
  193. // if title is a function, call it and set innerText or innerHtml depending by `allowHtml` value
  194. var titleText = title.call(reference);
  195. allowHtml ? titleNode.innerHTML = titleText : titleNode.innerText = titleText;
  196. } else {
  197. // if it's just a simple text, set innerText or innerHtml depending by `allowHtml` value
  198. allowHtml ? titleNode.innerHTML = title : titleNode.innerText = title;
  199. }
  200. // return the generated tooltip node
  201. return tooltipNode;
  202. }
  203. }, {
  204. key: '_show',
  205. value: function _show(reference, options) {
  206. // don't show if it's already visible
  207. if (this._isOpen) {
  208. return this;
  209. }
  210. this._isOpen = true;
  211. // if the tooltipNode already exists, just show it
  212. if (this._tooltipNode) {
  213. this._tooltipNode.style.display = '';
  214. this._tooltipNode.setAttribute('aria-hidden', 'false');
  215. this.popperInstance.update();
  216. return this;
  217. }
  218. // get title
  219. var title = reference.getAttribute('title') || options.title;
  220. // don't show tooltip if no title is defined
  221. if (!title) {
  222. return this;
  223. }
  224. // create tooltip node
  225. var tooltipNode = this._create(reference, options.template, title, options.html);
  226. // Add `aria-describedby` to our reference element for accessibility reasons
  227. reference.setAttribute('aria-describedby', tooltipNode.id);
  228. // append tooltip to container
  229. var container = this._findContainer(options.container, reference);
  230. this._append(tooltipNode, container);
  231. var popperOptions = _extends({}, options.popperOptions, {
  232. placement: options.placement
  233. });
  234. popperOptions.modifiers = _extends({}, popperOptions.modifiers, {
  235. arrow: {
  236. element: this.arrowSelector
  237. }
  238. });
  239. if (options.boundariesElement) {
  240. popperOptions.modifiers.preventOverflow = {
  241. boundariesElement: options.boundariesElement
  242. };
  243. }
  244. this.popperInstance = new Popper(reference, tooltipNode, popperOptions);
  245. this._tooltipNode = tooltipNode;
  246. return this;
  247. }
  248. }, {
  249. key: '_hide',
  250. value: function _hide() /*reference, options*/{
  251. // don't hide if it's already hidden
  252. if (!this._isOpen) {
  253. return this;
  254. }
  255. this._isOpen = false;
  256. // hide tooltipNode
  257. this._tooltipNode.style.display = 'none';
  258. this._tooltipNode.setAttribute('aria-hidden', 'true');
  259. return this;
  260. }
  261. }, {
  262. key: '_dispose',
  263. value: function _dispose() {
  264. var _this = this;
  265. if (this._tooltipNode) {
  266. this._hide();
  267. // destroy instance
  268. this.popperInstance.destroy();
  269. // remove event listeners
  270. this._events.forEach(function (_ref) {
  271. var func = _ref.func,
  272. event = _ref.event;
  273. _this.reference.removeEventListener(event, func);
  274. });
  275. this._events = [];
  276. // destroy tooltipNode
  277. this._tooltipNode.parentNode.removeChild(this._tooltipNode);
  278. this._tooltipNode = null;
  279. }
  280. return this;
  281. }
  282. }, {
  283. key: '_findContainer',
  284. value: function _findContainer(container, reference) {
  285. // if container is a query, get the relative element
  286. if (typeof container === 'string') {
  287. container = window.document.querySelector(container);
  288. } else if (container === false) {
  289. // if container is `false`, set it to reference parent
  290. container = reference.parentNode;
  291. }
  292. return container;
  293. }
  294. /**
  295. * Append tooltip to container
  296. * @memberof Tooltip
  297. * @private
  298. * @param {HTMLElement} tooltip
  299. * @param {HTMLElement|String|false} container
  300. */
  301. }, {
  302. key: '_append',
  303. value: function _append(tooltipNode, container) {
  304. container.appendChild(tooltipNode);
  305. }
  306. }, {
  307. key: '_setEventListeners',
  308. value: function _setEventListeners(reference, events, options) {
  309. var _this2 = this;
  310. var directEvents = [];
  311. var oppositeEvents = [];
  312. events.forEach(function (event) {
  313. switch (event) {
  314. case 'hover':
  315. directEvents.push('mouseenter');
  316. oppositeEvents.push('mouseleave');
  317. break;
  318. case 'focus':
  319. directEvents.push('focus');
  320. oppositeEvents.push('blur');
  321. break;
  322. case 'click':
  323. directEvents.push('click');
  324. oppositeEvents.push('click');
  325. break;
  326. }
  327. });
  328. // schedule show tooltip
  329. directEvents.forEach(function (event) {
  330. var func = function func(evt) {
  331. if (_this2._isOpen === true) {
  332. return;
  333. }
  334. evt.usedByTooltip = true;
  335. _this2._scheduleShow(reference, options.delay, options, evt);
  336. };
  337. _this2._events.push({ event: event, func: func });
  338. reference.addEventListener(event, func);
  339. });
  340. // schedule hide tooltip
  341. oppositeEvents.forEach(function (event) {
  342. var func = function func(evt) {
  343. if (evt.usedByTooltip === true) {
  344. return;
  345. }
  346. _this2._scheduleHide(reference, options.delay, options, evt);
  347. };
  348. _this2._events.push({ event: event, func: func });
  349. reference.addEventListener(event, func);
  350. });
  351. }
  352. }, {
  353. key: '_scheduleShow',
  354. value: function _scheduleShow(reference, delay, options /*, evt */) {
  355. var _this3 = this;
  356. // defaults to 0
  357. var computedDelay = delay && delay.show || delay || 0;
  358. window.setTimeout(function () {
  359. return _this3._show(reference, options);
  360. }, computedDelay);
  361. }
  362. }, {
  363. key: '_scheduleHide',
  364. value: function _scheduleHide(reference, delay, options, evt) {
  365. var _this4 = this;
  366. // defaults to 0
  367. var computedDelay = delay && delay.hide || delay || 0;
  368. window.setTimeout(function () {
  369. if (_this4._isOpen === false) {
  370. return;
  371. }
  372. if (!document.body.contains(_this4._tooltipNode)) {
  373. return;
  374. }
  375. // if we are hiding because of a mouseleave, we must check that the new
  376. // reference isn't the tooltip, because in this case we don't want to hide it
  377. if (evt.type === 'mouseleave') {
  378. var isSet = _this4._setTooltipNodeEvent(evt, reference, delay, options);
  379. // if we set the new event, don't hide the tooltip yet
  380. // the new event will take care to hide it if necessary
  381. if (isSet) {
  382. return;
  383. }
  384. }
  385. _this4._hide(reference, options);
  386. }, computedDelay);
  387. }
  388. }]);
  389. return Tooltip;
  390. }();
  391. /**
  392. * Placement function, its context is the Tooltip instance.
  393. * @memberof Tooltip
  394. * @callback PlacementFunction
  395. * @param {HTMLElement} tooltip - tooltip DOM node.
  396. * @param {HTMLElement} reference - reference DOM node.
  397. * @return {String} placement - One of the allowed placement options.
  398. */
  399. /**
  400. * Title function, its context is the Tooltip instance.
  401. * @memberof Tooltip
  402. * @callback TitleFunction
  403. * @return {String} placement - The desired title.
  404. */
  405. var _initialiseProps = function _initialiseProps() {
  406. var _this5 = this;
  407. this.show = function () {
  408. return _this5._show(_this5.reference, _this5.options);
  409. };
  410. this.hide = function () {
  411. return _this5._hide();
  412. };
  413. this.dispose = function () {
  414. return _this5._dispose();
  415. };
  416. this.toggle = function () {
  417. if (_this5._isOpen) {
  418. return _this5.hide();
  419. } else {
  420. return _this5.show();
  421. }
  422. };
  423. this.arrowSelector = '.tooltip-arrow, .tooltip__arrow';
  424. this.innerSelector = '.tooltip-inner, .tooltip__inner';
  425. this._events = [];
  426. this._setTooltipNodeEvent = function (evt, reference, delay, options) {
  427. var relatedreference = evt.relatedreference || evt.toElement;
  428. var callback = function callback(evt2) {
  429. var relatedreference2 = evt2.relatedreference || evt2.toElement;
  430. // Remove event listener after call
  431. _this5._tooltipNode.removeEventListener(evt.type, callback);
  432. // If the new reference is not the reference element
  433. if (!reference.contains(relatedreference2)) {
  434. // Schedule to hide tooltip
  435. _this5._scheduleHide(reference, options.delay, options, evt2);
  436. }
  437. };
  438. if (_this5._tooltipNode.contains(relatedreference)) {
  439. // listen to mouseleave on the tooltip element to be able to hide the tooltip
  440. _this5._tooltipNode.addEventListener(evt.type, callback);
  441. return true;
  442. }
  443. return false;
  444. };
  445. };
  446. return Tooltip;
  447. })));
  448. //# sourceMappingURL=tooltip.js.map