bootstrap-vue
Version:
BootstrapVue provides one of the most comprehensive implementations of Bootstrap 4 components and grid system for Vue.js and with extensive and automated WAI-ARIA accessibility markup.
1,109 lines (1,000 loc) • 32.9 kB
JavaScript
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
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; }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
import Popper from 'popper.js';
import BvEvent from './bv-event.class';
import { assign } from './object';
import { from as arrayFrom } from './array';
import { closest, select, isVisible, isDisabled, getCS, addClass, removeClass, hasClass, setAttr, removeAttr, getAttr, eventOn, eventOff } from './dom';
var NAME = 'tooltip';
var CLASS_PREFIX = 'bs-tooltip';
var BSCLS_PREFIX_REGEX = new RegExp('\\b' + CLASS_PREFIX + '\\S+', 'g');
var TRANSITION_DURATION = 150;
// Modal $root hidden event
var MODAL_CLOSE_EVENT = 'bv::modal::hidden';
// Modal container for appending tip/popover
var MODAL_CLASS = '.modal-content';
var AttachmentMap = {
AUTO: 'auto',
TOP: 'top',
RIGHT: 'right',
BOTTOM: 'bottom',
LEFT: 'left',
TOPLEFT: 'top',
TOPRIGHT: 'top',
RIGHTTOP: 'right',
RIGHTBOTTOM: 'right',
BOTTOMLEFT: 'bottom',
BOTTOMRIGHT: 'bottom',
LEFTTOP: 'left',
LEFTBOTTOM: 'left'
};
var OffsetMap = {
AUTO: 0,
TOPLEFT: -1,
TOP: 0,
TOPRIGHT: +1,
RIGHTTOP: -1,
RIGHT: 0,
RIGHTBOTTOM: +1,
BOTTOMLEFT: -1,
BOTTOM: 0,
BOTTOMRIGHT: +1,
LEFTTOP: -1,
LEFT: 0,
LEFTBOTTOM: +1
};
var HoverState = {
SHOW: 'show',
OUT: 'out'
};
var ClassName = {
FADE: 'fade',
SHOW: 'show'
};
var Selector = {
TOOLTIP: '.tooltip',
TOOLTIP_INNER: '.tooltip-inner',
ARROW: '.arrow'
// ESLINT: Not used
// const Trigger = {
// HOVER: 'hover',
// FOCUS: 'focus',
// CLICK: 'click',
// BLUR: 'blur',
// MANUAL: 'manual'
// }
};var Defaults = {
animation: true,
template: '<div class="tooltip" role="tooltip">' + '<div class="arrow"></div>' + '<div class="tooltip-inner"></div>' + '</div>',
trigger: 'hover focus',
title: '',
delay: 0,
html: false,
placement: 'top',
offset: 0,
arrowPadding: 6,
container: false,
fallbackPlacement: 'flip',
callbacks: {},
boundary: 'scrollParent'
// Transition Event names
};var TransitionEndEvents = {
WebkitTransition: ['webkitTransitionEnd'],
MozTransition: ['transitionend'],
OTransition: ['otransitionend', 'oTransitionEnd'],
transition: ['transitionend']
// Client Side Tip ID counter for aria-describedby attribute
// Could use Alex's uid generator util
// Each tooltip requires a unique client side ID
};var NEXTID = 1;
/* istanbul ignore next */
function generateId(name) {
return '__BV_' + name + '_' + NEXTID++ + '__';
}
/*
* ToolTip Class definition
*/
/* istanbul ignore next: difficult to test in Jest/JSDOM environment */
var ToolTip = function () {
// Main constructor
function ToolTip(element, config, $root) {
_classCallCheck(this, ToolTip);
// New tooltip object
this.$isEnabled = true;
this.$fadeTimeout = null;
this.$hoverTimeout = null;
this.$visibleInterval = null;
this.$hoverState = '';
this.$activeTrigger = {};
this.$popper = null;
this.$element = element;
this.$tip = null;
this.$id = generateId(this.constructor.NAME);
this.$root = $root || null;
this.$routeWatcher = null;
// We use a bound version of the following handlers for root/modal listeners to maintain the 'this' context
this.$forceHide = this.forceHide.bind(this);
this.$doHide = this.doHide.bind(this);
this.$doShow = this.doShow.bind(this);
this.$doDisable = this.doDisable.bind(this);
this.$doEnable = this.doEnable.bind(this);
// Set the configuration
this.updateConfig(config);
}
// NOTE: Overridden by PopOver class
_createClass(ToolTip, [{
key: 'updateConfig',
// Update config
value: function updateConfig(config) {
// Merge config into defaults. We use "this" here because PopOver overrides Default
var updatedConfig = assign({}, this.constructor.Default, config);
// Sanitize delay
if (config.delay && typeof config.delay === 'number') {
updatedConfig.delay = {
show: config.delay,
hide: config.delay
};
}
// Title for tooltip and popover
if (config.title && typeof config.title === 'number') {
updatedConfig.title = config.title.toString();
}
// Content only for popover
if (config.content && typeof config.content === 'number') {
updatedConfig.content = config.content.toString();
}
// Hide element original title if needed
this.fixTitle();
// Update the config
this.$config = updatedConfig;
// Stop/Restart listening
this.unListen();
this.listen();
}
// Destroy this instance
}, {
key: 'destroy',
value: function destroy() {
// Stop listening to trigger events
this.unListen();
// Disable while open listeners/watchers
this.setWhileOpenListeners(false);
// Clear any timouts
clearTimeout(this.$hoverTimeout);
this.$hoverTimeout = null;
clearTimeout(this.$fadeTimeout);
this.$fadeTimeout = null;
// Remove popper
if (this.$popper) {
this.$popper.destroy();
}
this.$popper = null;
// Remove tip from document
if (this.$tip && this.$tip.parentElement) {
this.$tip.parentElement.removeChild(this.$tip);
}
this.$tip = null;
// Null out other properties
this.$id = null;
this.$isEnabled = null;
this.$root = null;
this.$element = null;
this.$config = null;
this.$hoverState = null;
this.$activeTrigger = null;
this.$forceHide = null;
this.$doHide = null;
this.$doShow = null;
this.$doDisable = null;
this.$doEnable = null;
}
}, {
key: 'enable',
value: function enable() {
// Create a non-cancelable BvEvent
var enabledEvt = new BvEvent('enabled', {
cancelable: false,
target: this.$element,
relatedTarget: null
});
this.$isEnabled = true;
this.emitEvent(enabledEvt);
}
}, {
key: 'disable',
value: function disable() {
// Create a non-cancelable BvEvent
var disabledEvt = new BvEvent('disabled', {
cancelable: false,
target: this.$element,
relatedTarget: null
});
this.$isEnabled = false;
this.emitEvent(disabledEvt);
}
// Click toggler
}, {
key: 'toggle',
value: function toggle(event) {
if (!this.$isEnabled) {
return;
}
if (event) {
this.$activeTrigger.click = !this.$activeTrigger.click;
if (this.isWithActiveTrigger()) {
this.enter(null);
} else {
this.leave(null);
}
} else {
if (hasClass(this.getTipElement(), ClassName.SHOW)) {
this.leave(null);
} else {
this.enter(null);
}
}
}
// Show tooltip
}, {
key: 'show',
value: function show() {
var _this = this;
if (!document.body.contains(this.$element) || !isVisible(this.$element)) {
// If trigger element isn't in the DOM or is not visible
return;
}
// Build tooltip element (also sets this.$tip)
var tip = this.getTipElement();
this.fixTitle();
this.setContent(tip);
if (!this.isWithContent(tip)) {
// if No content, don't bother showing
this.$tip = null;
return;
}
// Set ID on tip and aria-describedby on element
setAttr(tip, 'id', this.$id);
this.addAriaDescribedby();
// Set animation on or off
if (this.$config.animation) {
addClass(tip, ClassName.FADE);
} else {
removeClass(tip, ClassName.FADE);
}
var placement = this.getPlacement();
var attachment = this.constructor.getAttachment(placement);
this.addAttachmentClass(attachment);
// Create a cancelable BvEvent
var showEvt = new BvEvent('show', {
cancelable: true,
target: this.$element,
relatedTarget: tip
});
this.emitEvent(showEvt);
if (showEvt.defaultPrevented) {
// Don't show if event cancelled
this.$tip = null;
return;
}
// Insert tooltip if needed
var container = this.getContainer();
if (!document.body.contains(tip)) {
container.appendChild(tip);
}
// Refresh popper
this.removePopper();
this.$popper = new Popper(this.$element, tip, this.getPopperConfig(placement, tip));
// Transitionend Callback
var complete = function complete() {
if (_this.$config.animation) {
_this.fixTransition(tip);
}
var prevHoverState = _this.$hoverState;
_this.$hoverState = null;
if (prevHoverState === HoverState.OUT) {
_this.leave(null);
}
// Create a non-cancelable BvEvent
var shownEvt = new BvEvent('shown', {
cancelable: false,
target: _this.$element,
relatedTarget: tip
});
_this.emitEvent(shownEvt);
};
// Enable while open listeners/watchers
this.setWhileOpenListeners(true);
// Show tip
addClass(tip, ClassName.SHOW);
// Start the transition/animation
this.transitionOnce(tip, complete);
}
// handler for periodic visibility check
}, {
key: 'visibleCheck',
value: function visibleCheck(on) {
var _this2 = this;
clearInterval(this.$visibleInterval);
this.$visibleInterval = null;
if (on) {
this.$visibleInterval = setInterval(function () {
var tip = _this2.getTipElement();
if (tip && !isVisible(_this2.$element) && hasClass(tip, ClassName.SHOW)) {
// Element is no longer visible, so force-hide the tooltip
_this2.forceHide();
}
}, 100);
}
}
}, {
key: 'setWhileOpenListeners',
value: function setWhileOpenListeners(on) {
// Modal close events
this.setModalListener(on);
// Periodic $element visibility check
// For handling when tip is in <keepalive>, tabs, carousel, etc
this.visibleCheck(on);
// Route change events
this.setRouteWatcher(on);
// Ontouch start listeners
this.setOnTouchStartListener(on);
if (on && /(focus|blur)/.test(this.$config.trigger)) {
// If focus moves between trigger element and tip container, dont close
eventOn(this.$tip, 'focusout', this);
} else {
eventOff(this.$tip, 'focusout', this);
}
}
// force hide of tip (internal method)
}, {
key: 'forceHide',
value: function forceHide() {
if (!this.$tip || !hasClass(this.$tip, ClassName.SHOW)) {
return;
}
// Disable while open listeners/watchers
this.setWhileOpenListeners(false);
// Clear any hover enter/leave event
clearTimeout(this.$hoverTimeout);
this.$hoverTimeout = null;
this.$hoverState = '';
// Hide the tip
this.hide(null, true);
}
// Hide tooltip
}, {
key: 'hide',
value: function hide(callback, force) {
var _this3 = this;
var tip = this.$tip;
if (!tip) {
return;
}
// Create a canelable BvEvent
var hideEvt = new BvEvent('hide', {
// We disable cancelling if force is true
cancelable: !force,
target: this.$element,
relatedTarget: tip
});
this.emitEvent(hideEvt);
if (hideEvt.defaultPrevented) {
// Don't hide if event cancelled
return;
}
// Transitionend Callback
/* istanbul ignore next */
var complete = function complete() {
if (_this3.$hoverState !== HoverState.SHOW && tip.parentNode) {
// Remove tip from dom, and force recompile on next show
tip.parentNode.removeChild(tip);
_this3.removeAriaDescribedby();
_this3.removePopper();
_this3.$tip = null;
}
if (callback) {
callback();
}
// Create a non-cancelable BvEvent
var hiddenEvt = new BvEvent('hidden', {
cancelable: false,
target: _this3.$element,
relatedTarget: null
});
_this3.emitEvent(hiddenEvt);
};
// Disable while open listeners/watchers
this.setWhileOpenListeners(false);
// If forced close, disable animation
if (force) {
removeClass(tip, ClassName.FADE);
}
// Hide tip
removeClass(tip, ClassName.SHOW);
this.$activeTrigger.click = false;
this.$activeTrigger.focus = false;
this.$activeTrigger.hover = false;
// Start the hide transition
this.transitionOnce(tip, complete);
this.$hoverState = '';
}
}, {
key: 'emitEvent',
value: function emitEvent(evt) {
var evtName = evt.type;
if (this.$root && this.$root.$emit) {
// Emit an event on $root
this.$root.$emit('bv::' + this.constructor.NAME + '::' + evtName, evt);
}
var callbacks = this.$config.callbacks || {};
if (typeof callbacks[evtName] === 'function') {
callbacks[evtName](evt);
}
}
}, {
key: 'getContainer',
value: function getContainer() {
var container = this.$config.container;
var body = document.body;
// If we are in a modal, we append to the modal instead of body, unless a container is specified
return container === false ? closest(MODAL_CLASS, this.$element) || body : select(container, body) || body;
}
// Will be overritten by popover if needed
}, {
key: 'addAriaDescribedby',
value: function addAriaDescribedby() {
// Add aria-describedby on trigger element, without removing any other IDs
var desc = getAttr(this.$element, 'aria-describedby') || '';
desc = desc.split(/\s+/).concat(this.$id).join(' ').trim();
setAttr(this.$element, 'aria-describedby', desc);
}
// Will be overritten by popover if needed
}, {
key: 'removeAriaDescribedby',
value: function removeAriaDescribedby() {
var _this4 = this;
var desc = getAttr(this.$element, 'aria-describedby') || '';
desc = desc.split(/\s+/).filter(function (d) {
return d !== _this4.$id;
}).join(' ').trim();
if (desc) {
setAttr(this.$element, 'aria-describedby', desc);
} else {
removeAttr(this.$element, 'aria-describedby');
}
}
}, {
key: 'removePopper',
value: function removePopper() {
if (this.$popper) {
this.$popper.destroy();
}
this.$popper = null;
}
/* istanbul ignore next */
}, {
key: 'transitionOnce',
value: function transitionOnce(tip, complete) {
var _this5 = this;
var transEvents = this.getTransitionEndEvents();
var called = false;
clearTimeout(this.$fadeTimeout);
this.$fadeTimeout = null;
var fnOnce = function fnOnce() {
if (called) {
return;
}
called = true;
clearTimeout(_this5.$fadeTimeout);
_this5.$fadeTimeout = null;
transEvents.forEach(function (evtName) {
eventOff(tip, evtName, fnOnce);
});
// Call complete callback
complete();
};
if (hasClass(tip, ClassName.FADE)) {
transEvents.forEach(function (evtName) {
eventOn(tip, evtName, fnOnce);
});
// Fallback to setTimeout
this.$fadeTimeout = setTimeout(fnOnce, TRANSITION_DURATION);
} else {
fnOnce();
}
}
// What transitionend event(s) to use? (returns array of event names)
}, {
key: 'getTransitionEndEvents',
value: function getTransitionEndEvents() {
for (var name in TransitionEndEvents) {
if (this.$element.style[name] !== undefined) {
return TransitionEndEvents[name];
}
}
// fallback
return [];
}
}, {
key: 'update',
value: function update() {
if (this.$popper !== null) {
this.$popper.scheduleUpdate();
}
}
// NOTE: Overridden by PopOver class
}, {
key: 'isWithContent',
value: function isWithContent(tip) {
tip = tip || this.$tip;
if (!tip) {
return false;
}
return Boolean((select(Selector.TOOLTIP_INNER, tip) || {}).innerHTML);
}
// NOTE: Overridden by PopOver class
}, {
key: 'addAttachmentClass',
value: function addAttachmentClass(attachment) {
addClass(this.getTipElement(), CLASS_PREFIX + '-' + attachment);
}
}, {
key: 'getTipElement',
value: function getTipElement() {
if (!this.$tip) {
// Try and compile user supplied template, or fallback to default template
this.$tip = this.compileTemplate(this.$config.template) || this.compileTemplate(this.constructor.Default.template);
}
// Add tab index so tip can be focused, and to allow it to be set as relatedTargt in focusin/out events
this.$tip.tabIndex = -1;
return this.$tip;
}
}, {
key: 'compileTemplate',
value: function compileTemplate(html) {
if (!html || typeof html !== 'string') {
return null;
}
var div = document.createElement('div');
div.innerHTML = html.trim();
var node = div.firstElementChild ? div.removeChild(div.firstElementChild) : null;
div = null;
return node;
}
// NOTE: Overridden by PopOver class
}, {
key: 'setContent',
value: function setContent(tip) {
this.setElementContent(select(Selector.TOOLTIP_INNER, tip), this.getTitle());
removeClass(tip, ClassName.FADE);
removeClass(tip, ClassName.SHOW);
}
}, {
key: 'setElementContent',
value: function setElementContent(container, content) {
if (!container) {
// If container element doesn't exist, just return
return;
}
var allowHtml = this.$config.html;
if ((typeof content === 'undefined' ? 'undefined' : _typeof(content)) === 'object' && content.nodeType) {
// content is a DOM node
if (allowHtml) {
if (content.parentElement !== container) {
container.innerHtml = '';
container.appendChild(content);
}
} else {
container.innerText = content.innerText;
}
} else {
// We have a plain HTML string or Text
container[allowHtml ? 'innerHTML' : 'innerText'] = content;
}
}
// NOTE: Overridden by PopOver class
}, {
key: 'getTitle',
value: function getTitle() {
var title = this.$config.title || '';
if (typeof title === 'function') {
// Call the function to get the title value
title = title(this.$element);
}
if ((typeof title === 'undefined' ? 'undefined' : _typeof(title)) === 'object' && title.nodeType && !title.innerHTML.trim()) {
// We have a DOM node, but without inner content, so just return empty string
title = '';
}
if (typeof title === 'string') {
title = title.trim();
}
if (!title) {
// If an explicit title is not given, try element's title atributes
title = getAttr(this.$element, 'title') || getAttr(this.$element, 'data-original-title') || '';
title = title.trim();
}
return title;
}
}, {
key: 'listen',
value: function listen() {
var _this6 = this;
var triggers = this.$config.trigger.trim().split(/\s+/);
var el = this.$element;
// Listen for global show/hide events
this.setRootListener(true);
// Using 'this' as the handler will get automagically directed to this.handleEvent
// And maintain our binding to 'this'
triggers.forEach(function (trigger) {
if (trigger === 'click') {
eventOn(el, 'click', _this6);
} else if (trigger === 'focus') {
eventOn(el, 'focusin', _this6);
eventOn(el, 'focusout', _this6);
} else if (trigger === 'blur') {
// Used to close $tip when element looses focus
eventOn(el, 'focusout', _this6);
} else if (trigger === 'hover') {
eventOn(el, 'mouseenter', _this6);
eventOn(el, 'mouseleave', _this6);
}
}, this);
}
}, {
key: 'unListen',
value: function unListen() {
var _this7 = this;
var events = ['click', 'focusin', 'focusout', 'mouseenter', 'mouseleave'];
// Using "this" as the handler will get automagically directed to this.handleEvent
events.forEach(function (evt) {
eventOff(_this7.$element, evt, _this7);
}, this);
// Stop listening for global show/hide/enable/disable events
this.setRootListener(false);
}
}, {
key: 'handleEvent',
value: function handleEvent(e) {
// This special method allows us to use "this" as the event handlers
if (isDisabled(this.$element)) {
// If disabled, don't do anything. Note: if tip is shown before element gets
// disabled, then tip not close until no longer disabled or forcefully closed.
return;
}
if (!this.$isEnabled) {
// If not enable
return;
}
var type = e.type;
var target = e.target;
var relatedTarget = e.relatedTarget;
var $element = this.$element;
var $tip = this.$tip;
if (type === 'click') {
this.toggle(e);
} else if (type === 'focusin' || type === 'mouseenter') {
this.enter(e);
} else if (type === 'focusout') {
// target is the element which is loosing focus
// And relatedTarget is the element gaining focus
if ($tip && $element && $element.contains(target) && $tip.contains(relatedTarget)) {
// If focus moves from $element to $tip, don't trigger a leave
return;
}
if ($tip && $element && $tip.contains(target) && $element.contains(relatedTarget)) {
// If focus moves from $tip to $element, don't trigger a leave
return;
}
if ($tip && $tip.contains(target) && $tip.contains(relatedTarget)) {
// If focus moves within $tip, don't trigger a leave
return;
}
if ($element && $element.contains(target) && $element.contains(relatedTarget)) {
// If focus moves within $element, don't trigger a leave
return;
}
// Otherwise trigger a leave
this.leave(e);
} else if (type === 'mouseleave') {
this.leave(e);
}
}
/* istanbul ignore next */
}, {
key: 'setRouteWatcher',
value: function setRouteWatcher(on) {
var _this8 = this;
if (on) {
this.setRouteWatcher(false);
if (this.$root && Boolean(this.$root.$route)) {
this.$routeWatcher = this.$root.$watch('$route', function (newVal, oldVal) {
if (newVal === oldVal) {
return;
}
// If route has changed, we force hide the tooltip/popover
_this8.forceHide();
});
}
} else {
if (this.$routeWatcher) {
// cancel the route watcher by calling hte stored reference
this.$routeWatcher();
this.$routeWatcher = null;
}
}
}
/* istanbul ignore next */
}, {
key: 'setModalListener',
value: function setModalListener(on) {
var modal = closest(MODAL_CLASS, this.$element);
if (!modal) {
// If we are not in a modal, don't worry. be happy
return;
}
// We can listen for modal hidden events on $root
if (this.$root) {
this.$root[on ? '$on' : '$off'](MODAL_CLOSE_EVENT, this.$forceHide);
}
}
/* istanbul ignore next */
}, {
key: 'setRootListener',
value: function setRootListener(on) {
// Listen for global 'bv::{hide|show}::{tooltip|popover}' hide request event
if (this.$root) {
this.$root[on ? '$on' : '$off']('bv::hide::' + this.constructor.NAME, this.$doHide);
this.$root[on ? '$on' : '$off']('bv::show::' + this.constructor.NAME, this.$doShow);
this.$root[on ? '$on' : '$off']('bv::disable::' + this.constructor.NAME, this.$doDisable);
this.$root[on ? '$on' : '$off']('bv::enable::' + this.constructor.NAME, this.$doEnable);
}
}
}, {
key: 'doHide',
value: function doHide(id) {
// Programmatically hide tooltip or popover
if (!id) {
// Close all tooltips or popovers
this.forceHide();
} else if (this.$element && this.$element.id && this.$element.id === id) {
// Close this specific tooltip or popover
this.hide();
}
}
}, {
key: 'doShow',
value: function doShow(id) {
// Programmatically show tooltip or popover
if (!id) {
// Open all tooltips or popovers
this.show();
} else if (id && this.$element && this.$element.id && this.$element.id === id) {
// Show this specific tooltip or popover
this.show();
}
}
}, {
key: 'doDisable',
value: function doDisable(id) {
// Programmatically disable tooltip or popover
if (!id) {
// Disable all tooltips or popovers
this.disable();
} else if (this.$element && this.$element.id && this.$element.id === id) {
// Disable this specific tooltip or popover
this.disable();
}
}
}, {
key: 'doEnable',
value: function doEnable(id) {
// Programmatically enable tooltip or popover
if (!id) {
// Enable all tooltips or popovers
this.enable();
} else if (this.$element && this.$element.id && this.$element.id === id) {
// Enable this specific tooltip or popover
this.enable();
}
}
/* istanbul ignore next */
}, {
key: 'setOnTouchStartListener',
value: function setOnTouchStartListener(on) {
var _this9 = this;
// if this is a touch-enabled device we add extra
// empty mouseover listeners to the body's immediate children;
// only needed because of broken event delegation on iOS
// https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html
if ('ontouchstart' in document.documentElement) {
arrayFrom(document.body.children).forEach(function (el) {
if (on) {
eventOn(el, 'mouseover', _this9._noop);
} else {
eventOff(el, 'mouseover', _this9._noop);
}
});
}
}
/* istanbul ignore next */
}, {
key: '_noop',
value: function _noop() {
// Empty noop handler for ontouchstart devices
}
}, {
key: 'fixTitle',
value: function fixTitle() {
var el = this.$element;
var titleType = _typeof(getAttr(el, 'data-original-title'));
if (getAttr(el, 'title') || titleType !== 'string') {
setAttr(el, 'data-original-title', getAttr(el, 'title') || '');
setAttr(el, 'title', '');
}
}
// Enter handler
/* istanbul ignore next */
}, {
key: 'enter',
value: function enter(e) {
var _this10 = this;
if (e) {
this.$activeTrigger[e.type === 'focusin' ? 'focus' : 'hover'] = true;
}
if (hasClass(this.getTipElement(), ClassName.SHOW) || this.$hoverState === HoverState.SHOW) {
this.$hoverState = HoverState.SHOW;
return;
}
clearTimeout(this.$hoverTimeout);
this.$hoverState = HoverState.SHOW;
if (!this.$config.delay || !this.$config.delay.show) {
this.show();
return;
}
this.$hoverTimeout = setTimeout(function () {
if (_this10.$hoverState === HoverState.SHOW) {
_this10.show();
}
}, this.$config.delay.show);
}
// Leave handler
/* istanbul ignore next */
}, {
key: 'leave',
value: function leave(e) {
var _this11 = this;
if (e) {
this.$activeTrigger[e.type === 'focusout' ? 'focus' : 'hover'] = false;
if (e.type === 'focusout' && /blur/.test(this.$config.trigger)) {
// Special case for `blur`: we clear out the other triggers
this.$activeTrigger.click = false;
this.$activeTrigger.hover = false;
}
}
if (this.isWithActiveTrigger()) {
return;
}
clearTimeout(this.$hoverTimeout);
this.$hoverState = HoverState.OUT;
if (!this.$config.delay || !this.$config.delay.hide) {
this.hide();
return;
}
this.$hoverTimeout = setTimeout(function () {
if (_this11.$hoverState === HoverState.OUT) {
_this11.hide();
}
}, this.$config.delay.hide);
}
}, {
key: 'getPopperConfig',
value: function getPopperConfig(placement, tip) {
var _this12 = this;
return {
placement: this.constructor.getAttachment(placement),
modifiers: {
offset: { offset: this.getOffset(placement, tip) },
flip: { behavior: this.$config.fallbackPlacement },
arrow: { element: '.arrow' },
preventOverflow: { boundariesElement: this.$config.boundary }
},
onCreate: function onCreate(data) {
// Handle flipping arrow classes
if (data.originalPlacement !== data.placement) {
_this12.handlePopperPlacementChange(data);
}
},
onUpdate: function onUpdate(data) {
// Handle flipping arrow classes
_this12.handlePopperPlacementChange(data);
}
};
}
}, {
key: 'getOffset',
value: function getOffset(placement, tip) {
if (!this.$config.offset) {
var arrow = select(Selector.ARROW, tip);
var arrowOffset = parseFloat(getCS(arrow).width) + parseFloat(this.$config.arrowPadding);
switch (OffsetMap[placement.toUpperCase()]) {
case +1:
return '+50%p - ' + arrowOffset + 'px';
case -1:
return '-50%p + ' + arrowOffset + 'px';
default:
return 0;
}
}
return parseFloat(this.$config.offset);
}
}, {
key: 'getPlacement',
value: function getPlacement() {
var placement = this.$config.placement;
if (typeof placement === 'function') {
return placement.call(this, this.$tip, this.$element);
}
return placement;
}
}, {
key: 'isWithActiveTrigger',
value: function isWithActiveTrigger() {
for (var trigger in this.$activeTrigger) {
if (this.$activeTrigger[trigger]) {
return true;
}
}
return false;
}
// NOTE: Overridden by PopOver class
}, {
key: 'cleanTipClass',
value: function cleanTipClass() {
var tip = this.getTipElement();
var tabClass = tip.className.match(BSCLS_PREFIX_REGEX);
if (tabClass !== null && tabClass.length > 0) {
tabClass.forEach(function (cls) {
removeClass(tip, cls);
});
}
}
}, {
key: 'handlePopperPlacementChange',
value: function handlePopperPlacementChange(data) {
this.cleanTipClass();
this.addAttachmentClass(this.constructor.getAttachment(data.placement));
}
}, {
key: 'fixTransition',
value: function fixTransition(tip) {
var initConfigAnimation = this.$config.animation || false;
if (getAttr(tip, 'x-placement') !== null) {
return;
}
removeClass(tip, ClassName.FADE);
this.$config.animation = false;
this.hide();
this.show();
this.$config.animation = initConfigAnimation;
}
}], [{
key: 'getAttachment',
value: function getAttachment(placement) {
return AttachmentMap[placement.toUpperCase()];
}
}, {
key: 'Default',
get: function get() {
return Defaults;
}
// NOTE: Overridden by PopOver class
}, {
key: 'NAME',
get: function get() {
return NAME;
}
}]);
return ToolTip;
}();
export default ToolTip;