123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456 |
- 'use strict';
- import $ from 'jquery';
- import { GetYoDigits, ignoreMousedisappear } from './foundation.core.utils';
- import { MediaQuery } from './foundation.util.mediaQuery';
- import { Triggers } from './foundation.util.triggers';
- import { Positionable } from './foundation.positionable';
- /**
- * Tooltip module.
- * @module foundation.tooltip
- * @requires foundation.util.box
- * @requires foundation.util.mediaQuery
- * @requires foundation.util.triggers
- */
- class Tooltip extends Positionable {
- /**
- * Creates a new instance of a Tooltip.
- * @class
- * @name Tooltip
- * @fires Tooltip#init
- * @param {jQuery} element - jQuery object to attach a tooltip to.
- * @param {Object} options - object to extend the default configuration.
- */
- _setup(element, options) {
- this.$element = element;
- this.options = $.extend({}, Tooltip.defaults, this.$element.data(), options);
- this.className = 'Tooltip'; // ie9 back compat
- this.isActive = false;
- this.isClick = false;
- // Triggers init is idempotent, just need to make sure it is initialized
- Triggers.init($);
- this._init();
- }
- /**
- * Initializes the tooltip by setting the creating the tip element, adding it's text, setting private variables and setting attributes on the anchor.
- * @private
- */
- _init() {
- MediaQuery._init();
- var elemId = this.$element.attr('aria-describedby') || GetYoDigits(6, 'tooltip');
- this.options.tipText = this.options.tipText || this.$element.attr('title');
- this.template = this.options.template ? $(this.options.template) : this._buildTemplate(elemId);
- if (this.options.allowHtml) {
- this.template.appendTo(document.body)
- .html(this.options.tipText)
- .hide();
- } else {
- this.template.appendTo(document.body)
- .text(this.options.tipText)
- .hide();
- }
- this.$element.attr({
- 'title': '',
- 'aria-describedby': elemId,
- 'data-yeti-box': elemId,
- 'data-toggle': elemId,
- 'data-resize': elemId
- }).addClass(this.options.triggerClass);
- super._init();
- this._events();
- }
- _getDefaultPosition() {
- // handle legacy classnames
- var position = this.$element[0].className.match(/\b(top|left|right|bottom)\b/g);
- return position ? position[0] : 'top';
- }
- _getDefaultAlignment() {
- return 'center';
- }
- _getHOffset() {
- if(this.position === 'left' || this.position === 'right') {
- return this.options.hOffset + this.options.tooltipWidth;
- } else {
- return this.options.hOffset
- }
- }
- _getVOffset() {
- if(this.position === 'top' || this.position === 'bottom') {
- return this.options.vOffset + this.options.tooltipHeight;
- } else {
- return this.options.vOffset
- }
- }
- /**
- * builds the tooltip element, adds attributes, and returns the template.
- * @private
- */
- _buildTemplate(id) {
- var templateClasses = (`${this.options.tooltipClass} ${this.options.templateClasses}`).trim();
- var $template = $('<div></div>').addClass(templateClasses).attr({
- 'role': 'tooltip',
- 'aria-hidden': true,
- 'data-is-active': false,
- 'data-is-focus': false,
- 'id': id
- });
- return $template;
- }
- /**
- * sets the position class of an element and recursively calls itself until there are no more possible positions to attempt, or the tooltip element is no longer colliding.
- * if the tooltip is larger than the screen width, default to full width - any user selected margin
- * @private
- */
- _setPosition() {
- super._setPosition(this.$element, this.template);
- }
- /**
- * reveals the tooltip, and fires an event to close any other open tooltips on the page
- * @fires Tooltip#closeme
- * @fires Tooltip#show
- * @function
- */
- show() {
- if (this.options.showOn !== 'all' && !MediaQuery.is(this.options.showOn)) {
- // console.error('The screen is too small to display this tooltip');
- return false;
- }
- var _this = this;
- this.template.css('visibility', 'hidden').show();
- this._setPosition();
- this.template.removeClass('top bottom left right').addClass(this.position)
- this.template.removeClass('align-top align-bottom align-left align-right align-center').addClass('align-' + this.alignment);
- /**
- * Fires to close all other open tooltips on the page
- * @event Closeme#tooltip
- */
- this.$element.trigger('closeme.zf.tooltip', this.template.attr('id'));
- this.template.attr({
- 'data-is-active': true,
- 'aria-hidden': false
- });
- _this.isActive = true;
- // console.log(this.template);
- this.template.stop().hide().css('visibility', '').fadeIn(this.options.fadeInDuration, function() {
- //maybe do stuff?
- });
- /**
- * Fires when the tooltip is shown
- * @event Tooltip#show
- */
- this.$element.trigger('show.zf.tooltip');
- }
- /**
- * Hides the current tooltip, and resets the positioning class if it was changed due to collision
- * @fires Tooltip#hide
- * @function
- */
- hide() {
- // console.log('hiding', this.$element.data('yeti-box'));
- var _this = this;
- this.template.stop().attr({
- 'aria-hidden': true,
- 'data-is-active': false
- }).fadeOut(this.options.fadeOutDuration, function() {
- _this.isActive = false;
- _this.isClick = false;
- });
- /**
- * fires when the tooltip is hidden
- * @event Tooltip#hide
- */
- this.$element.trigger('hide.zf.tooltip');
- }
- /**
- * adds event listeners for the tooltip and its anchor
- * TODO combine some of the listeners like focus and mouseenter, etc.
- * @private
- */
- _events() {
- var _this = this;
- var $template = this.template;
- var isFocus = false;
- if (!this.options.disableHover) {
- this.$element
- .on('mouseenter.zf.tooltip', function(e) {
- if (!_this.isActive) {
- _this.timeout = setTimeout(function() {
- _this.show();
- }, _this.options.hoverDelay);
- }
- })
- .on('mouseleave.zf.tooltip', ignoreMousedisappear(function(e) {
- clearTimeout(_this.timeout);
- if (!isFocus || (_this.isClick && !_this.options.clickOpen)) {
- _this.hide();
- }
- }));
- }
- if (this.options.clickOpen) {
- this.$element.on('mousedown.zf.tooltip', function(e) {
- e.stopImmediatePropagation();
- if (_this.isClick) {
- //_this.hide();
- // _this.isClick = false;
- } else {
- _this.isClick = true;
- if ((_this.options.disableHover || !_this.$element.attr('tabindex')) && !_this.isActive) {
- _this.show();
- }
- }
- });
- } else {
- this.$element.on('mousedown.zf.tooltip', function(e) {
- e.stopImmediatePropagation();
- _this.isClick = true;
- });
- }
- if (!this.options.disableForTouch) {
- this.$element
- .on('tap.zf.tooltip touchend.zf.tooltip', function(e) {
- _this.isActive ? _this.hide() : _this.show();
- });
- }
- this.$element.on({
- // 'toggle.zf.trigger': this.toggle.bind(this),
- // 'close.zf.trigger': this.hide.bind(this)
- 'close.zf.trigger': this.hide.bind(this)
- });
- this.$element
- .on('focus.zf.tooltip', function(e) {
- isFocus = true;
- if (_this.isClick) {
- // If we're not showing open on clicks, we need to pretend a click-launched focus isn't
- // a real focus, otherwise on hover and come back we get bad behavior
- if(!_this.options.clickOpen) { isFocus = false; }
- return false;
- } else {
- _this.show();
- }
- })
- .on('focusout.zf.tooltip', function(e) {
- isFocus = false;
- _this.isClick = false;
- _this.hide();
- })
- .on('resizeme.zf.trigger', function() {
- if (_this.isActive) {
- _this._setPosition();
- }
- });
- }
- /**
- * adds a toggle method, in addition to the static show() & hide() functions
- * @function
- */
- toggle() {
- if (this.isActive) {
- this.hide();
- } else {
- this.show();
- }
- }
- /**
- * Destroys an instance of tooltip, removes template element from the view.
- * @function
- */
- _destroy() {
- this.$element.attr('title', this.template.text())
- .off('.zf.trigger .zf.tooltip')
- .removeClass(this.options.triggerClass)
- .removeClass('top right left bottom')
- .removeAttr('aria-describedby data-disable-hover data-resize data-toggle data-tooltip data-yeti-box');
- this.template.remove();
- }
- }
- Tooltip.defaults = {
- disableForTouch: false,
- /**
- * Time, in ms, before a tooltip should open on hover.
- * @option
- * @type {number}
- * @default 200
- */
- hoverDelay: 200,
- /**
- * Time, in ms, a tooltip should take to fade into view.
- * @option
- * @type {number}
- * @default 150
- */
- fadeInDuration: 150,
- /**
- * Time, in ms, a tooltip should take to fade out of view.
- * @option
- * @type {number}
- * @default 150
- */
- fadeOutDuration: 150,
- /**
- * Disables hover events from opening the tooltip if set to true
- * @option
- * @type {boolean}
- * @default false
- */
- disableHover: false,
- /**
- * Optional addtional classes to apply to the tooltip template on init.
- * @option
- * @type {string}
- * @default ''
- */
- templateClasses: '',
- /**
- * Non-optional class added to tooltip templates. Foundation default is 'tooltip'.
- * @option
- * @type {string}
- * @default 'tooltip'
- */
- tooltipClass: 'tooltip',
- /**
- * Class applied to the tooltip anchor element.
- * @option
- * @type {string}
- * @default 'has-tip'
- */
- triggerClass: 'has-tip',
- /**
- * Minimum breakpoint size at which to open the tooltip.
- * @option
- * @type {string}
- * @default 'small'
- */
- showOn: 'small',
- /**
- * Custom template to be used to generate markup for tooltip.
- * @option
- * @type {string}
- * @default ''
- */
- template: '',
- /**
- * Text displayed in the tooltip template on open.
- * @option
- * @type {string}
- * @default ''
- */
- tipText: '',
- touchCloseText: 'Tap to close.',
- /**
- * Allows the tooltip to remain open if triggered with a click or touch event.
- * @option
- * @type {boolean}
- * @default true
- */
- clickOpen: true,
- /**
- * Position of tooltip. Can be left, right, bottom, top, or auto.
- * @option
- * @type {string}
- * @default 'auto'
- */
- position: 'auto',
- /**
- * Alignment of tooltip relative to anchor. Can be left, right, bottom, top, center, or auto.
- * @option
- * @type {string}
- * @default 'auto'
- */
- alignment: 'auto',
- /**
- * Allow overlap of container/window. If false, tooltip will first try to
- * position as defined by data-position and data-alignment, but reposition if
- * it would cause an overflow. @option
- * @type {boolean}
- * @default false
- */
- allowOverlap: false,
- /**
- * Allow overlap of only the bottom of the container. This is the most common
- * behavior for dropdowns, allowing the dropdown to extend the bottom of the
- * screen but not otherwise influence or break out of the container.
- * Less common for tooltips.
- * @option
- * @type {boolean}
- * @default false
- */
- allowBottomOverlap: false,
- /**
- * Distance, in pixels, the template should push away from the anchor on the Y axis.
- * @option
- * @type {number}
- * @default 0
- */
- vOffset: 0,
- /**
- * Distance, in pixels, the template should push away from the anchor on the X axis
- * @option
- * @type {number}
- * @default 0
- */
- hOffset: 0,
- /**
- * Distance, in pixels, the template spacing auto-adjust for a vertical tooltip
- * @option
- * @type {number}
- * @default 14
- */
- tooltipHeight: 14,
- /**
- * Distance, in pixels, the template spacing auto-adjust for a horizontal tooltip
- * @option
- * @type {number}
- * @default 12
- */
- tooltipWidth: 12,
- /**
- * Allow HTML in tooltip. Warning: If you are loading user-generated content into tooltips,
- * allowing HTML may open yourself up to XSS attacks.
- * @option
- * @type {boolean}
- * @default false
- */
- allowHtml: false
- };
- /**
- * TODO utilize resize event trigger
- */
- export {Tooltip};
|