1
0
mirror of https://github.com/twbs/bootstrap.git synced 2025-08-15 10:05:40 +02:00

rewritten tooltip + tether integration and death to our positioner jank

This commit is contained in:
fat
2015-05-11 23:32:37 -07:00
parent 8eee78ca15
commit 3452e8dc83
18 changed files with 5405 additions and 4536 deletions

View File

@@ -24,7 +24,7 @@ var ScrollSpy = (function ($) {
var DATA_KEY = 'bs.scrollspy';
var JQUERY_NO_CONFLICT = $.fn[NAME];
var Defaults = {
var Default = {
offset: 10
};

File diff suppressed because one or more lines are too long

2
js/dist/tab.js.map vendored

File diff suppressed because one or more lines are too long

597
js/dist/tooltip.js vendored Normal file
View File

@@ -0,0 +1,597 @@
'use strict';
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'); } }
/**
* --------------------------------------------------------------------------
* Bootstrap (v4.0.0): alert.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* --------------------------------------------------------------------------
*/
var ToolTip = (function ($) {
/**
* ------------------------------------------------------------------------
* Constants
* ------------------------------------------------------------------------
*/
var NAME = 'tooltip';
var VERSION = '4.0.0';
var DATA_KEY = 'bs.tooltip';
var JQUERY_NO_CONFLICT = $.fn[NAME];
var TRANSITION_DURATION = 150;
var CLASS_PREFIX = 'bs-tether';
var Default = {
animation: true,
template: '<div class="tooltip" role="tooltip">' + '<div class="tooltip-arrow"></div>' + '<div class="tooltip-inner"></div></div>',
trigger: 'hover focus',
title: '',
delay: 0,
html: false,
selector: false,
attachment: 'top',
offset: '0 0',
constraints: null
};
var HorizontalMirror = {
LEFT: 'right',
CENTER: 'center',
RIGHT: 'left'
};
var VerticalMirror = {
TOP: 'bottom',
MIDDLE: 'middle',
BOTTOM: 'top'
};
var VerticalDefault = {
LEFT: 'middle',
CENTER: 'bottom',
RIGHT: 'middle'
};
var HorizontalDefault = {
TOP: 'center',
MIDDLE: 'left',
BOTTOM: 'center'
};
var HoverState = {
IN: 'in',
OUT: 'out'
};
var Event = {
HIDE: 'hide.bs.tooltip',
HIDDEN: 'hidden.bs.tooltip',
SHOW: 'show.bs.tooltip',
SHOWN: 'shown.bs.tooltip',
INSERTED: 'inserted.bs.tooltip',
CLICK: 'click.bs.tooltip',
FOCUSIN: 'focusin.bs.tooltip',
FOCUSOUT: 'focusout.bs.tooltip',
MOUSEENTER: 'mouseenter.bs.tooltip',
MOUSELEAVE: 'mouseleave.bs.tooltip'
};
var ClassName = {
FADE: 'fade',
IN: 'in'
};
var Selector = {
TOOLTIP: '.tooltip',
TOOLTIP_INNER: '.tooltip-inner',
TOOLTIP_ARROW: '.tooltip-arrow'
};
var TetherClass = {
'element': false,
'enabled': false
};
/**
* ------------------------------------------------------------------------
* Class Definition
* ------------------------------------------------------------------------
*/
var Tooltip = (function () {
function Tooltip(element, config) {
_classCallCheck(this, Tooltip);
// private
this._isEnabled = true;
this._timeout = 0;
this._hoverState = '';
this._activeTrigger = {};
// protected
this.element = element;
this.config = this._getConfig(config);
this.tip = null;
this.tether = null;
this._setListeners();
}
_createClass(Tooltip, [{
key: 'enable',
// public
value: function enable() {
this._isEnabled = true;
}
}, {
key: 'disable',
value: function disable() {
this._isEnabled = false;
}
}, {
key: 'toggleEnabled',
value: function toggleEnabled() {
this._isEnabled = !this._isEnabled;
}
}, {
key: 'toggle',
value: function toggle(event) {
var context = this;
if (event) {
context = $(event.currentTarget).data(DATA_KEY);
if (!context) {
context = new this.constructor(event.currentTarget, this._getDelegateConfig());
$(event.currentTarget).data(DATA_KEY, context);
}
context._activeTrigger.click = !context._activeTrigger.click;
if (context._isWithActiveTrigger()) {
context._enter(null, context);
} else {
context._leave(null, context);
}
} else {
$(context.getTipElement()).hasClass(ClassName.IN) ? context._leave(null, context) : context._enter(null, context);
}
}
}, {
key: 'destroy',
value: function destroy() {
var _this = this;
clearTimeout(this._timeout);
this.hide(function () {
$(_this.element).off(Selector.TOOLTIP).removeData(DATA_KEY);
});
}
}, {
key: 'show',
value: function show() {
var _this2 = this;
var showEvent = $.Event(Event.SHOW);
if (this.isWithContent() && this._isEnabled) {
$(this.element).trigger(showEvent);
var isInTheDom = $.contains(this.element.ownerDocument.documentElement, this.element);
if (showEvent.isDefaultPrevented() || !isInTheDom) {
return;
}
var tip = this.getTipElement();
var tipId = Util.getUID(NAME);
tip.setAttribute('id', tipId);
this.element.setAttribute('aria-describedby', tipId);
this.setContent();
if (this.config.animation) {
$(tip).addClass(ClassName.FADE);
}
var attachment = typeof this.config.attachment === 'function' ? this.config.attachment.call(this, tip, this.element) : this.config.attachment;
attachment = this.getAttachment(attachment);
$(tip).data(DATA_KEY, this);
this.element.parentNode.insertBefore(tip, this.element.nextSibling);
$(this.element).trigger(Event.INSERTED);
this.tether = new Tether({
element: this.tip,
target: this.element,
attachment: attachment,
classes: TetherClass,
classPrefix: CLASS_PREFIX,
offset: this.config.offset,
constraints: this.config.constraints
});
Util.reflow(tip);
this.tether.position();
$(tip).addClass(ClassName.IN);
var complete = function complete() {
var prevHoverState = _this2._hoverState;
_this2._hoverState = null;
$(_this2.element).trigger(Event.SHOWN);
if (prevHoverState === HoverState.OUT) {
_this2._leave(null, _this2);
}
};
Util.supportsTransitionEnd() && $(this.tip).hasClass(ClassName.FADE) ? $(this.tip).one(Util.TRANSITION_END, complete).emulateTransitionEnd(Tooltip._TRANSITION_DURATION) : complete();
}
}
}, {
key: 'hide',
value: function hide(callback) {
var _this3 = this;
var tip = this.getTipElement();
var hideEvent = $.Event(Event.HIDE);
var complete = function complete() {
if (_this3._hoverState !== HoverState.IN && tip.parentNode) {
tip.parentNode.removeChild(tip);
}
_this3.element.removeAttribute('aria-describedby');
$(_this3.element).trigger(Event.HIDDEN);
_this3.cleanupTether();
if (callback) {
callback();
}
};
$(this.element).trigger(hideEvent);
if (hideEvent.isDefaultPrevented()) {
return;
}
$(tip).removeClass(ClassName.IN);
if (Util.supportsTransitionEnd() && $(this.tip).hasClass(ClassName.FADE)) {
$(tip).one(Util.TRANSITION_END, complete).emulateTransitionEnd(TRANSITION_DURATION);
} else {
complete();
}
this._hoverState = '';
}
}, {
key: 'isWithContent',
// protected
value: function isWithContent() {
return !!this.getTitle();
}
}, {
key: 'getTipElement',
value: function getTipElement() {
return this.tip = this.tip || $(this.config.template)[0];
}
}, {
key: 'getAttachment',
value: function getAttachment(attachmentString) {
var attachmentArray = attachmentString.split(' ');
var normalizedAttachment = {};
if (!attachmentArray.length) {
throw new Error('Tooltip requires attachment');
}
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = attachmentArray[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var attachment = _step.value;
attachment = attachment.toUpperCase();
if (HorizontalMirror[attachment]) {
normalizedAttachment.horizontal = HorizontalMirror[attachment];
}
if (VerticalMirror[attachment]) {
normalizedAttachment.vertical = VerticalMirror[attachment];
}
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator['return']) {
_iterator['return']();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
if (!normalizedAttachment.horizontal && !normalizedAttachment.vertical) {
throw new Error('Tooltip requires valid attachment');
}
if (!normalizedAttachment.horizontal) {
normalizedAttachment.horizontal = HorizontalDefault[normalizedAttachment.vertical.toUpperCase()];
}
if (!normalizedAttachment.vertical) {
normalizedAttachment.vertical = VerticalDefault[normalizedAttachment.horizontal.toUpperCase()];
}
return [normalizedAttachment.vertical, normalizedAttachment.horizontal].join(' ');
}
}, {
key: 'setContent',
value: function setContent() {
var tip = this.getTipElement();
var title = this.getTitle();
var method = this.config.html ? 'innerHTML' : 'innerText';
$(tip).find(Selector.TOOLTIP_INNER)[0][method] = title;
$(tip).removeClass(ClassName.FADE).removeClass(ClassName.IN);
this.cleanupTether();
}
}, {
key: 'getTitle',
value: function getTitle() {
var title = this.element.getAttribute('data-original-title');
if (!title) {
title = typeof this.config.title === 'function' ? this.config.title.call(this.element) : this.config.title;
}
return title;
}
}, {
key: 'removeTetherClasses',
value: function removeTetherClasses(i, css) {
return ((css.baseVal || css).match(new RegExp('(^|\\s)' + CLASS_PREFIX + '-\\S+', 'g')) || []).join(' ');
}
}, {
key: 'cleanupTether',
value: function cleanupTether() {
if (this.tether) {
this.tether.destroy();
// clean up after tether's junk classes
// remove after they fix issue
// (https://github.com/HubSpot/tether/issues/36)
$(this.element).removeClass(this.removeTetherClasses);
$(this.tip).removeClass(this.removeTetherClasses);
}
}
}, {
key: '_setListeners',
// private
value: function _setListeners() {
var _this4 = this;
var triggers = this.config.trigger.split(' ');
triggers.forEach(function (trigger) {
if (trigger === 'click') {
$(_this4.element).on(Event.CLICK, _this4.config.selector, _this4.toggle.bind(_this4));
} else if (trigger !== 'manual') {
var eventIn = trigger == 'hover' ? Event.MOUSEENTER : Event.FOCUSIN;
var eventOut = trigger == 'hover' ? Event.MOUSELEAVE : Event.FOCUSOUT;
$(_this4.element).on(eventIn, _this4.config.selector, _this4._enter.bind(_this4)).on(eventOut, _this4.config.selector, _this4._leave.bind(_this4));
}
});
if (this.config.selector) {
this.config = $.extend({}, this.config, {
trigger: 'manual',
selector: ''
});
} else {
this._fixTitle();
}
}
}, {
key: '_fixTitle',
value: function _fixTitle() {
var titleType = typeof this.element.getAttribute('data-original-title');
if (this.element.getAttribute('title') || titleType !== 'string') {
this.element.setAttribute('data-original-title', this.element.getAttribute('title') || '');
this.element.setAttribute('title', '');
}
}
}, {
key: '_enter',
value: function _enter(event, context) {
context = context || $(event.currentTarget).data(DATA_KEY);
if (!context) {
context = new this.constructor(event.currentTarget, this._getDelegateConfig());
$(event.currentTarget).data(DATA_KEY, context);
}
if (event) {
context._activeTrigger[event.type == 'focusin' ? 'focus' : 'hover'] = true;
}
if ($(context.getTipElement()).hasClass('in') || context._hoverState === 'in') {
context._hoverState = 'in';
return;
}
clearTimeout(context._timeout);
context._hoverState = HoverState.IN;
if (!context.config.delay || !context.config.delay.show) {
context.show();
return;
}
context._timeout = setTimeout(function () {
if (context._hoverState === HoverState.IN) {
context.show();
}
}, context.config.delay.show);
}
}, {
key: '_leave',
value: function _leave(event, context) {
context = context || $(event.currentTarget).data(DATA_KEY);
if (!context) {
context = new this.constructor(event.currentTarget, this._getDelegateConfig());
$(event.currentTarget).data(DATA_KEY, context);
}
if (event) {
context._activeTrigger[event.type == 'focusout' ? 'focus' : 'hover'] = false;
}
if (context._isWithActiveTrigger()) {
return;
}
clearTimeout(context._timeout);
context._hoverState = HoverState.OUT;
if (!context.config.delay || !context.config.delay.hide) {
context.hide();
return;
}
context._timeout = setTimeout(function () {
if (context._hoverState === HoverState.OUT) {
context.hide();
}
}, context.config.delay.hide);
}
}, {
key: '_isWithActiveTrigger',
value: function _isWithActiveTrigger() {
for (var trigger in this._activeTrigger) {
if (this._activeTrigger[trigger]) {
return true;
}
}
return false;
}
}, {
key: '_getConfig',
value: function _getConfig(config) {
config = $.extend({}, Default, $(this.element).data(), config);
if (config.delay && typeof config.delay === 'number') {
config.delay = {
'show': config.delay,
'hide': config.delay
};
}
return config;
}
}, {
key: '_getDelegateConfig',
value: function _getDelegateConfig() {
var config = {};
if (this.config) {
for (var key in this.config) {
var value = this.config[key];
if (Default[key] !== value) {
config[key] = value;
}
}
}
return config;
}
}], [{
key: 'VERSION',
// getters
get: function () {
return VERSION;
}
}, {
key: 'Default',
get: function () {
return Default;
}
}, {
key: '_jQueryInterface',
// static
value: function _jQueryInterface(config) {
return this.each(function () {
var data = $(this).data(DATA_KEY);
var _config = typeof config === 'object' ? config : null;
if (!data && /destroy|hide/.test(config)) {
return;
}
if (!data) {
data = new Tooltip(this, _config);
$(this).data(DATA_KEY, data);
}
if (typeof config === 'string') {
data[config]();
}
});
}
}]);
return Tooltip;
})();
/**
* ------------------------------------------------------------------------
* jQuery
* ------------------------------------------------------------------------
*/
$.fn[NAME] = Tooltip._jQueryInterface;
$.fn[NAME].Constructor = Tooltip;
$.fn[NAME].noConflict = function () {
$.fn[NAME] = JQUERY_NO_CONFLICT;
return Tooltip._jQueryInterface;
};
return Tooltip;
})(jQuery);
//# sourceMappingURL=tooltip.js.map

1
js/dist/tooltip.js.map vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -22,7 +22,7 @@ const ScrollSpy = (($) => {
const DATA_KEY = 'bs.scrollspy'
const JQUERY_NO_CONFLICT = $.fn[NAME]
const Defaults = {
const Default = {
offset : 10
}

619
js/src/tooltip.js Normal file
View File

@@ -0,0 +1,619 @@
import Util from './util'
/**
* --------------------------------------------------------------------------
* Bootstrap (v4.0.0): tooltip.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* --------------------------------------------------------------------------
*/
const ToolTip = (($) => {
/**
* ------------------------------------------------------------------------
* Constants
* ------------------------------------------------------------------------
*/
const NAME = 'tooltip'
const VERSION = '4.0.0'
const DATA_KEY = 'bs.tooltip'
const JQUERY_NO_CONFLICT = $.fn[NAME]
const TRANSITION_DURATION = 150
const CLASS_PREFIX = 'bs-tether'
const Default = {
animation : true,
template : '<div class="tooltip" role="tooltip">'
+ '<div class="tooltip-arrow"></div>'
+ '<div class="tooltip-inner"></div></div>',
trigger : 'hover focus',
title : '',
delay : 0,
html : false,
selector : false,
attachment : 'top',
offset : '0 0',
constraints : null
}
const HorizontalMirror = {
LEFT : 'right',
CENTER : 'center',
RIGHT : 'left'
}
const VerticalMirror = {
TOP : 'bottom',
MIDDLE : 'middle',
BOTTOM : 'top'
}
const VerticalDefault = {
LEFT : 'middle',
CENTER : 'bottom',
RIGHT : 'middle'
}
const HorizontalDefault = {
TOP : 'center',
MIDDLE : 'left',
BOTTOM : 'center'
}
const HoverState = {
IN : 'in',
OUT : 'out'
}
const Event = {
HIDE : 'hide.bs.tooltip',
HIDDEN : 'hidden.bs.tooltip',
SHOW : 'show.bs.tooltip',
SHOWN : 'shown.bs.tooltip',
INSERTED : 'inserted.bs.tooltip',
CLICK : 'click.bs.tooltip',
FOCUSIN : 'focusin.bs.tooltip',
FOCUSOUT : 'focusout.bs.tooltip',
MOUSEENTER : 'mouseenter.bs.tooltip',
MOUSELEAVE : 'mouseleave.bs.tooltip'
}
const ClassName = {
FADE : 'fade',
IN : 'in'
}
const Selector = {
TOOLTIP : '.tooltip',
TOOLTIP_INNER : '.tooltip-inner',
TOOLTIP_ARROW : '.tooltip-arrow'
}
const TetherClass = {
element : false,
enabled : false
}
const Trigger = {
HOVER : 'hover',
FOCUS : 'focus',
CLICK : 'click',
MANUAL : 'manual'
}
/**
* ------------------------------------------------------------------------
* Class Definition
* ------------------------------------------------------------------------
*/
class Tooltip {
constructor(element, config) {
// private
this._isEnabled = true
this._timeout = 0
this._hoverState = ''
this._activeTrigger = {}
// protected
this.element = element
this.config = this._getConfig(config)
this.tip = null
this.tether = null
this._setListeners()
}
// getters
static get VERSION() {
return VERSION
}
static get Default() {
return Default
}
// public
enable() {
this._isEnabled = true
}
disable() {
this._isEnabled = false
}
toggleEnabled() {
this._isEnabled = !this._isEnabled
}
toggle(event) {
let context = this
if (event) {
context = $(event.currentTarget).data(DATA_KEY)
if (!context) {
context = new this.constructor(
event.currentTarget,
this._getDelegateConfig()
)
$(event.currentTarget).data(DATA_KEY, context)
}
context._activeTrigger.click = !context._activeTrigger.click
if (context._isWithActiveTrigger()) {
context._enter(null, context)
} else {
context._leave(null, context)
}
} else {
$(context.getTipElement()).hasClass(ClassName.IN) ?
context._leave(null, context) :
context._enter(null, context)
}
}
destroy() {
clearTimeout(this._timeout)
this.hide(() => {
$(this.element)
.off(Selector.TOOLTIP)
.removeData(DATA_KEY)
})
}
show() {
let showEvent = $.Event(Event.SHOW)
if (this.isWithContent() && this._isEnabled) {
$(this.element).trigger(showEvent)
let isInTheDom = $.contains(
this.element.ownerDocument.documentElement,
this.element
)
if (showEvent.isDefaultPrevented() || !isInTheDom) {
return
}
let tip = this.getTipElement()
let tipId = Util.getUID(NAME)
tip.setAttribute('id', tipId)
this.element.setAttribute('aria-describedby', tipId)
this.setContent()
if (this.config.animation) {
$(tip).addClass(ClassName.FADE)
}
let attachment = typeof this.config.attachment === 'function' ?
this.config.attachment.call(this, tip, this.element) :
this.config.attachment
attachment = this.getAttachment(attachment)
$(tip).data(DATA_KEY, this)
this.element.parentNode.insertBefore(tip, this.element.nextSibling)
$(this.element).trigger(Event.INSERTED)
this.tether = new Tether({
element : this.tip,
target : this.element,
attachment : attachment,
classes : TetherClass,
classPrefix : CLASS_PREFIX,
offset : this.config.offset,
constraints : this.config.constraints
})
Util.reflow(tip)
this.tether.position()
$(tip).addClass(ClassName.IN)
let complete = () => {
let prevHoverState = this._hoverState
this._hoverState = null
$(this.element).trigger(Event.SHOWN)
if (prevHoverState === HoverState.OUT) {
this._leave(null, this)
}
}
Util.supportsTransitionEnd() && $(this.tip).hasClass(ClassName.FADE) ?
$(this.tip)
.one(Util.TRANSITION_END, complete)
.emulateTransitionEnd(Tooltip._TRANSITION_DURATION) :
complete()
}
}
hide(callback) {
let tip = this.getTipElement()
let hideEvent = $.Event(Event.HIDE)
let complete = () => {
if (this._hoverState !== HoverState.IN && tip.parentNode) {
tip.parentNode.removeChild(tip)
}
this.element.removeAttribute('aria-describedby')
$(this.element).trigger(Event.HIDDEN)
this.cleanupTether()
if (callback) {
callback()
}
}
$(this.element).trigger(hideEvent)
if (hideEvent.isDefaultPrevented()) {
return
}
$(tip).removeClass(ClassName.IN)
if (Util.supportsTransitionEnd() &&
($(this.tip).hasClass(ClassName.FADE))) {
$(tip)
.one(Util.TRANSITION_END, complete)
.emulateTransitionEnd(TRANSITION_DURATION)
} else {
complete()
}
this._hoverState = ''
}
// protected
isWithContent() {
return !!this.getTitle()
}
getTipElement() {
return (this.tip = this.tip || $(this.config.template)[0])
}
getAttachment(attachmentString) {
let attachmentArray = attachmentString.split(' ')
let normalizedAttachment = {}
if (!attachmentArray.length) {
throw new Error('Tooltip requires attachment')
}
for (let attachment of attachmentArray) {
attachment = attachment.toUpperCase()
if (HorizontalMirror[attachment]) {
normalizedAttachment.horizontal = HorizontalMirror[attachment]
}
if (VerticalMirror[attachment]) {
normalizedAttachment.vertical = VerticalMirror[attachment]
}
}
if (!normalizedAttachment.horizontal &&
(!normalizedAttachment.vertical)) {
throw new Error('Tooltip requires valid attachment')
}
if (!normalizedAttachment.horizontal) {
normalizedAttachment.horizontal =
HorizontalDefault[normalizedAttachment.vertical.toUpperCase()]
}
if (!normalizedAttachment.vertical) {
normalizedAttachment.vertical =
VerticalDefault[normalizedAttachment.horizontal.toUpperCase()]
}
return [
normalizedAttachment.vertical,
normalizedAttachment.horizontal
].join(' ')
}
setContent() {
let tip = this.getTipElement()
let title = this.getTitle()
let method = this.config.html ? 'innerHTML' : 'innerText'
$(tip).find(Selector.TOOLTIP_INNER)[0][method] = title
$(tip)
.removeClass(ClassName.FADE)
.removeClass(ClassName.IN)
this.cleanupTether()
}
getTitle() {
let title = this.element.getAttribute('data-original-title')
if (!title) {
title = typeof this.config.title === 'function' ?
this.config.title.call(this.element) :
this.config.title
}
return title
}
removeTetherClasses(i, css) {
return ((css.baseVal || css).match(
new RegExp(`(^|\\s)${CLASS_PREFIX}-\\S+`, 'g')) || []
).join(' ')
}
cleanupTether() {
if (this.tether) {
this.tether.destroy()
// clean up after tether's junk classes
// remove after they fix issue
// (https://github.com/HubSpot/tether/issues/36)
$(this.element).removeClass(this.removeTetherClasses)
$(this.tip).removeClass(this.removeTetherClasses)
}
}
// private
_setListeners() {
let triggers = this.config.trigger.split(' ')
triggers.forEach((trigger) => {
if (trigger === 'click') {
$(this.element).on(
Event.CLICK,
this.config.selector,
this.toggle.bind(this)
)
} else if (trigger !== Trigger.MANUAL) {
let eventIn = trigger == Trigger.HOVER ?
Event.MOUSEENTER : Event.FOCUSIN
let eventOut = trigger == Trigger.HOVER ?
Event.MOUSELEAVE : Event.FOCUSOUT
$(this.element)
.on(
eventIn,
this.config.selector,
this._enter.bind(this)
)
.on(
eventOut,
this.config.selector,
this._leave.bind(this)
)
}
})
if (this.config.selector) {
this.config = $.extend({}, this.config, {
trigger : 'manual',
selector : ''
})
} else {
this._fixTitle()
}
}
_fixTitle() {
let titleType = typeof this.element.getAttribute('data-original-title')
if (this.element.getAttribute('title') ||
(titleType !== 'string')) {
this.element.setAttribute(
'data-original-title',
this.element.getAttribute('title') || ''
)
this.element.setAttribute('title', '')
}
}
_enter(event, context) {
context = context || $(event.currentTarget).data(DATA_KEY)
if (!context) {
context = new this.constructor(
event.currentTarget,
this._getDelegateConfig()
)
$(event.currentTarget).data(DATA_KEY, context)
}
if (event) {
context._activeTrigger[
event.type == 'focusin' ? Trigger.FOCUS : Trigger.HOVER
] = true
}
if ($(context.getTipElement()).hasClass(ClassName.IN) ||
(context._hoverState === HoverState.IN)) {
context._hoverState = HoverState.IN
return
}
clearTimeout(context._timeout)
context._hoverState = HoverState.IN
if (!context.config.delay || !context.config.delay.show) {
context.show()
return
}
context._timeout = setTimeout(() => {
if (context._hoverState === HoverState.IN) {
context.show()
}
}, context.config.delay.show)
}
_leave(event, context) {
context = context || $(event.currentTarget).data(DATA_KEY)
if (!context) {
context = new this.constructor(
event.currentTarget,
this._getDelegateConfig()
)
$(event.currentTarget).data(DATA_KEY, context)
}
if (event) {
context._activeTrigger[
event.type == 'focusout' ? Triger.FOCUS : Trigger.HOVER
] = false
}
if (context._isWithActiveTrigger()) {
return
}
clearTimeout(context._timeout)
context._hoverState = HoverState.OUT
if (!context.config.delay || !context.config.delay.hide) {
context.hide()
return
}
context._timeout = setTimeout(() => {
if (context._hoverState === HoverState.OUT) {
context.hide()
}
}, context.config.delay.hide)
}
_isWithActiveTrigger() {
for (let trigger in this._activeTrigger) {
if (this._activeTrigger[trigger]) {
return true
}
}
return false
}
_getConfig(config) {
config = $.extend({}, Default, $(this.element).data(), config)
if (config.delay && typeof config.delay === 'number') {
config.delay = {
show : config.delay,
hide : config.delay
}
}
return config
}
_getDelegateConfig() {
let config = {}
if (this.config) {
for (let key in this.config) {
let value = this.config[key]
if (Default[key] !== value) {
config[key] = value
}
}
}
return config
}
// static
static _jQueryInterface(config) {
return this.each(function () {
let data = $(this).data(DATA_KEY)
let _config = typeof config === 'object' ?
config : null
if (!data && /destroy|hide/.test(config)) {
return
}
if (!data) {
data = new Tooltip(this, _config)
$(this).data(DATA_KEY, data)
}
if (typeof config === 'string') {
data[config]()
}
})
}
}
/**
* ------------------------------------------------------------------------
* jQuery
* ------------------------------------------------------------------------
*/
$.fn[NAME] = Tooltip._jQueryInterface
$.fn[NAME].Constructor = Tooltip
$.fn[NAME].noConflict = function () {
$.fn[NAME] = JQUERY_NO_CONFLICT
return Tooltip._jQueryInterface
}
return Tooltip
})(jQuery)
export default Tooltip

155
js/tab.js
View File

@@ -1,155 +0,0 @@
/* ========================================================================
* Bootstrap: tab.js v3.3.4
* http://getbootstrap.com/javascript/#tabs
* ========================================================================
* Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// TAB CLASS DEFINITION
// ====================
var Tab = function (element) {
// jscs:disable requireDollarBeforejQueryAssignment
this.element = $(element)
// jscs:enable requireDollarBeforejQueryAssignment
}
Tab.VERSION = '3.3.4'
Tab.TRANSITION_DURATION = 150
Tab.prototype.show = function () {
var $this = this.element
var $ul = $this.closest('ul:not(.dropdown-menu)')
var selector = $this.data('target')
if (!selector) {
selector = $this.attr('href')
selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
}
if ($this.parent('li').hasClass('active')) return
var $previous = $ul.find('.active:last a')
var hideEvent = $.Event('hide.bs.tab', {
relatedTarget: $this[0]
})
var showEvent = $.Event('show.bs.tab', {
relatedTarget: $previous[0]
})
$previous.trigger(hideEvent)
$this.trigger(showEvent)
if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) return
var $target = $(selector)
this.activate($this.closest('li'), $ul)
this.activate($target, $target.parent(), function () {
$previous.trigger({
type: 'hidden.bs.tab',
relatedTarget: $this[0]
})
$this.trigger({
type: 'shown.bs.tab',
relatedTarget: $previous[0]
})
})
}
Tab.prototype.activate = function (element, container, callback) {
var $active = container.find('> .active')
var transition = callback
&& $.support.transition
&& ($active.length && $active.hasClass('fade') || !!container.find('> .fade').length)
function next() {
$active
.removeClass('active')
.find('> .dropdown-menu > .active')
.removeClass('active')
.end()
.find('[data-toggle="tab"]')
.attr('aria-expanded', false)
element
.addClass('active')
.find('[data-toggle="tab"]')
.attr('aria-expanded', true)
if (transition) {
element[0].offsetWidth // reflow for transition
element.addClass('in')
} else {
element.removeClass('fade')
}
if (element.parent('.dropdown-menu').length) {
element
.closest('li.dropdown')
.addClass('active')
.end()
.find('[data-toggle="tab"]')
.attr('aria-expanded', true)
}
callback && callback()
}
$active.length && transition ?
$active
.one('bsTransitionEnd', next)
.emulateTransitionEnd(Tab.TRANSITION_DURATION) :
next()
$active.removeClass('in')
}
// TAB PLUGIN DEFINITION
// =====================
function Plugin(option) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.tab')
if (!data) $this.data('bs.tab', (data = new Tab(this)))
if (typeof option == 'string') data[option]()
})
}
var old = $.fn.tab
$.fn.tab = Plugin
$.fn.tab.Constructor = Tab
// TAB NO CONFLICT
// ===============
$.fn.tab.noConflict = function () {
$.fn.tab = old
return this
}
// TAB DATA-API
// ============
var clickHandler = function (e) {
e.preventDefault()
Plugin.call($(this), 'show')
}
$(document)
.on('click.bs.tab.data-api', '[data-toggle="tab"]', clickHandler)
.on('click.bs.tab.data-api', '[data-toggle="pill"]', clickHandler)
}(jQuery);

View File

@@ -7,6 +7,7 @@
<!-- jQuery -->
<script src="vendor/jquery.min.js"></script>
<script src="vendor/tether.min.js"></script>
<script>
// Disable jQuery event aliases to ensure we don't accidentally use any of them
(function () {
@@ -139,12 +140,12 @@
<script src="../../js/dist/modal.js"></script>
<script src="../../js/dist/scrollspy.js"></script>
<script src="../../js/dist/tab.js"></script>
<script src="../../js/dist/tooltip.js"></script>
<!-- Old Plugin sources -->
<script src="../../js/tooltip.js"></script>
<script src="../../js/popover.js"></script>
<!-- <script src="../../js/popover.js"></script> -->
<!-- Unit tests -->
<!-- Unit tests
<script src="unit/alert.js"></script>
<script src="unit/button.js"></script>
<script src="unit/carousel.js"></script>
@@ -152,9 +153,9 @@
<script src="unit/dropdown.js"></script>
<script src="unit/modal.js"></script>
<script src="unit/scrollspy.js"></script>
<script src="unit/tab.js"></script>
<script src="unit/tab.js"></script> -->
<script src="unit/tooltip.js"></script>
<script src="unit/popover.js"></script>
<!-- <script src="unit/popover.js"></script> -->
</head>
<body>

View File

@@ -16,6 +16,7 @@ $(function () {
afterEach: function () {
$.fn.tooltip = $.fn.bootstrapTooltip
delete $.fn.bootstrapTooltip
$('.tooltip').remove()
}
})
@@ -34,7 +35,7 @@ $(function () {
QUnit.test('should expose default settings', function (assert) {
assert.expect(1)
assert.ok($.fn.bootstrapTooltip.Constructor.DEFAULTS, 'defaults is defined')
assert.ok($.fn.bootstrapTooltip.Constructor.Default, 'defaults is defined')
})
QUnit.test('should empty title attribute', function (assert) {
@@ -88,17 +89,21 @@ $(function () {
assert.strictEqual(id.indexOf('tooltip'), 0, 'tooltip id has prefix')
})
QUnit.test('should place tooltips relative to placement option', function (assert) {
QUnit.test('should place tooltips relative to attachment option', function (assert) {
assert.expect(2)
var $tooltip = $('<a href="#" rel="tooltip" title="Another tooltip"/>')
.appendTo('#qunit-fixture')
.bootstrapTooltip({ placement: 'bottom' })
.bootstrapTooltip({ attachment: 'bottom' })
$tooltip.bootstrapTooltip('show')
assert.ok($('.tooltip').is('.fade.bottom.in'), 'has correct classes applied')
assert
.ok($('.tooltip')
.is('.fade.bs-tether-element-attached-top.bs-tether-element-attached-center.in'), 'has correct classes applied')
$tooltip.bootstrapTooltip('hide')
assert.strictEqual($('.tooltip').length, 0, 'tooltip removed')
assert.strictEqual($tooltip.data('bs.tooltip').tip.parentNode, null, 'tooltip removed')
})
QUnit.test('should allow html entities', function (assert) {
@@ -111,7 +116,7 @@ $(function () {
assert.notEqual($('.tooltip b').length, 0, 'b tag was inserted')
$tooltip.bootstrapTooltip('hide')
assert.strictEqual($('.tooltip').length, 0, 'tooltip removed')
assert.strictEqual($tooltip.data('bs.tooltip').tip.parentNode, null, 'tooltip removed')
})
QUnit.test('should respect custom classes', function (assert) {
@@ -124,7 +129,7 @@ $(function () {
assert.ok($('.tooltip').hasClass('some-class'), 'custom class is present')
$tooltip.bootstrapTooltip('hide')
assert.strictEqual($('.tooltip').length, 0, 'tooltip removed')
assert.strictEqual($tooltip.data('bs.tooltip').tip.parentNode, null, 'tooltip removed')
})
QUnit.test('should fire show event', function (assert) {
@@ -253,21 +258,21 @@ $(function () {
assert.ok(!$._data($tooltip[0], 'events').mouseover && !$._data($tooltip[0], 'events').mouseout, 'tooltip does not have hover events')
})
QUnit.test('should show tooltip with delegate selector on click', function (assert) {
assert.expect(2)
var $div = $('<div><a href="#" rel="tooltip" title="Another tooltip"/></div>')
.appendTo('#qunit-fixture')
.bootstrapTooltip({
selector: 'a[rel="tooltip"]',
trigger: 'click'
})
// QUnit.test('should show tooltip with delegate selector on click', function (assert) {
// assert.expect(2)
// var $div = $('<div><a href="#" rel="tooltip" title="Another tooltip"/></div>')
// .appendTo('#qunit-fixture')
// .bootstrapTooltip({
// selector: 'a[rel="tooltip"]',
// trigger: 'click'
// })
$div.find('a').trigger('click')
assert.ok($('.tooltip').is('.fade.in'), 'tooltip is faded in')
// $div.find('a').trigger('click')
// assert.ok($('.tooltip').is('.fade.in'), 'tooltip is faded in')
$div.find('a').trigger('click')
assert.strictEqual($('.tooltip').length, 0, 'tooltip was removed from dom')
})
// $div.find('a').trigger('click')
// assert.strictEqual($div.data('bs.tooltip').tip.parentNode, null, 'tooltip removed')
// })
QUnit.test('should show tooltip when toggle is called', function (assert) {
assert.expect(1)
@@ -307,20 +312,20 @@ $(function () {
QUnit.test('should add position class before positioning so that position-specific styles are taken into account', function (assert) {
assert.expect(1)
var styles = '<style>'
+ '.tooltip.right { white-space: nowrap; }'
+ '.tooltip.right .tooltip-inner { max-width: none; }'
+ '</style>'
+ '.tooltip.right { white-space: nowrap; }'
+ '.tooltip.right .tooltip-inner { max-width: none; }'
+ '</style>'
var $styles = $(styles).appendTo('head')
var $container = $('<div/>').appendTo('#qunit-fixture')
var $target = $('<a href="#" rel="tooltip" title="very very very very very very very very long tooltip in one line"/>')
.appendTo($container)
.bootstrapTooltip({
placement: 'right',
viewport: null
attachment: 'right',
})
.bootstrapTooltip('show')
var $tooltip = $container.find('.tooltip')
var $tooltip = $($target.data('bs.tooltip').tip)
// this is some dumb hack shit because sub pixels in firefox
var top = Math.round($target.offset().top + ($target[0].offsetHeight / 2) - ($tooltip[0].offsetHeight / 2))
@@ -376,444 +381,7 @@ $(function () {
assert.strictEqual($('.tooltip').length, 0, 'tooltip removed from dom')
})
QUnit.test('should be placed dynamically to viewport with the dynamic placement option', function (assert) {
assert.expect(6)
var $style = $('<style> div[rel="tooltip"] { position: absolute; } #qunit-fixture { top: inherit; left: inherit } </style>').appendTo('head')
var $container = $('<div/>')
.css({
position: 'relative',
height: '100%'
})
.appendTo('#qunit-fixture')
var $topTooltip = $('<div style="left: 0; top: 0;" rel="tooltip" title="Top tooltip">Top Dynamic Tooltip</div>')
.appendTo($container)
.bootstrapTooltip({ placement: 'auto', viewport: '#qunit-fixture' })
$topTooltip.bootstrapTooltip('show')
assert.ok($('.tooltip').is('.bottom'), 'top positioned tooltip is dynamically positioned to bottom')
$topTooltip.bootstrapTooltip('hide')
assert.strictEqual($('.tooltip').length, 0, 'top positioned tooltip removed from dom')
var $rightTooltip = $('<div style="right: 0;" rel="tooltip" title="Right tooltip">Right Dynamic Tooltip</div>')
.appendTo($container)
.bootstrapTooltip({ placement: 'right auto', viewport: '#qunit-fixture' })
$rightTooltip.bootstrapTooltip('show')
assert.ok($('.tooltip').is('.left'), 'right positioned tooltip is dynamically positioned left')
$rightTooltip.bootstrapTooltip('hide')
assert.strictEqual($('.tooltip').length, 0, 'right positioned tooltip removed from dom')
var $leftTooltip = $('<div style="left: 0;" rel="tooltip" title="Left tooltip">Left Dynamic Tooltip</div>')
.appendTo($container)
.bootstrapTooltip({ placement: 'auto left', viewport: '#qunit-fixture' })
$leftTooltip.bootstrapTooltip('show')
assert.ok($('.tooltip').is('.right'), 'left positioned tooltip is dynamically positioned right')
$leftTooltip.bootstrapTooltip('hide')
assert.strictEqual($('.tooltip').length, 0, 'left positioned tooltip removed from dom')
$container.remove()
$style.remove()
})
QUnit.test('should position tip on top if viewport has enough space and placement is "auto top"', function (assert) {
assert.expect(2)
var styles = '<style>'
+ 'body { padding-top: 100px; }'
+ '#section { height: 300px; border: 1px solid red; padding-top: 50px }'
+ 'div[rel="tooltip"] { width: 150px; border: 1px solid blue; }'
+ '</style>'
var $styles = $(styles).appendTo('head')
var $container = $('<div id="section"/>').appendTo('#qunit-fixture')
var $target = $('<div rel="tooltip" title="tip"/>')
.appendTo($container)
.bootstrapTooltip({
placement: 'auto top',
viewport: '#section'
})
$target.bootstrapTooltip('show')
assert.ok($('.tooltip').is('.top'), 'top positioned tooltip is dynamically positioned to top')
$target.bootstrapTooltip('hide')
assert.strictEqual($('.tooltip').length, 0, 'tooltip removed from dom')
$styles.remove()
})
QUnit.test('should position tip on top if viewport has enough space and is not parent', function (assert) {
assert.expect(2)
var styles = '<style>'
+ '#section { height: 300px; border: 1px solid red; margin-top: 100px; }'
+ 'div[rel="tooltip"] { width: 150px; border: 1px solid blue; }'
+ '</style>'
var $styles = $(styles).appendTo('head')
var $container = $('<div id="section"/>').appendTo('#qunit-fixture')
var $target = $('<div rel="tooltip" title="tip"/>')
.appendTo($container)
.bootstrapTooltip({
placement: 'auto top',
viewport: '#qunit-fixture'
})
$target.bootstrapTooltip('show')
assert.ok($('.tooltip').is('.top'), 'top positioned tooltip is dynamically positioned to top')
$target.bootstrapTooltip('hide')
assert.strictEqual($('.tooltip').length, 0, 'tooltip removed from dom')
$styles.remove()
})
QUnit.test('should position tip on bottom if the tip\'s dimension exceeds the viewport area and placement is "auto top"', function (assert) {
assert.expect(2)
var styles = '<style>'
+ 'body { padding-top: 100px; }'
+ '#section { height: 300px; border: 1px solid red; }'
+ 'div[rel="tooltip"] { width: 150px; border: 1px solid blue; }'
+ '</style>'
var $styles = $(styles).appendTo('head')
var $container = $('<div id="section"/>').appendTo('#qunit-fixture')
var $target = $('<div rel="tooltip" title="tip"/>')
.appendTo($container)
.bootstrapTooltip({
placement: 'auto top',
viewport: '#section'
})
$target.bootstrapTooltip('show')
assert.ok($('.tooltip').is('.bottom'), 'top positioned tooltip is dynamically positioned to bottom')
$target.bootstrapTooltip('hide')
assert.strictEqual($('.tooltip').length, 0, 'tooltip removed from dom')
$styles.remove()
})
QUnit.test('should display the tip on top whenever scrollable viewport has enough room if the given placement is "auto top"', function (assert) {
assert.expect(2)
var styles = '<style>'
+ '#scrollable-div { height: 200px; overflow: auto; }'
+ '.tooltip-item { margin: 200px 0 400px; width: 150px; }'
+ '</style>'
var $styles = $(styles).appendTo('head')
var $container = $('<div id="scrollable-div"/>').appendTo('#qunit-fixture')
var $target = $('<div rel="tooltip" title="tip" class="tooltip-item">Tooltip Item</div>')
.appendTo($container)
.bootstrapTooltip({
placement: 'top auto',
viewport: '#scrollable-div'
})
$('#scrollable-div').scrollTop(100)
$target.bootstrapTooltip('show')
assert.ok($('.tooltip').is('.fade.top.in'), 'has correct classes applied')
$target.bootstrapTooltip('hide')
assert.strictEqual($('.tooltip').length, 0, 'tooltip removed from dom')
$styles.remove()
})
QUnit.test('should display the tip on bottom whenever scrollable viewport doesn\'t have enough room if the given placement is "auto top"', function (assert) {
assert.expect(2)
var styles = '<style>'
+ '#scrollable-div { height: 200px; overflow: auto; }'
+ '.tooltip-item { padding: 200px 0 400px; width: 150px; }'
+ '</style>'
var $styles = $(styles).appendTo('head')
var $container = $('<div id="scrollable-div"/>').appendTo('#qunit-fixture')
var $target = $('<div rel="tooltip" title="tip" class="tooltip-item">Tooltip Item</div>')
.appendTo($container)
.bootstrapTooltip({
placement: 'top auto',
viewport: '#scrollable-div'
})
$('#scrollable-div').scrollTop(200)
$target.bootstrapTooltip('show')
assert.ok($('.tooltip').is('.fade.bottom.in'), 'has correct classes applied')
$target.bootstrapTooltip('hide')
assert.strictEqual($('.tooltip').length, 0, 'tooltip removed from dom')
$styles.remove()
})
QUnit.test('should display the tip on bottom whenever scrollable viewport has enough room if the given placement is "auto bottom"', function (assert) {
assert.expect(2)
var styles = '<style>'
+ '#scrollable-div { height: 200px; overflow: auto; }'
+ '.spacer { height: 400px; }'
+ '.spacer:first-child { height: 200px; }'
+ '.tooltip-item { width: 150px; }'
+ '</style>'
var $styles = $(styles).appendTo('head')
var $container = $('<div id="scrollable-div"/>').appendTo('#qunit-fixture')
var $target = $('<div rel="tooltip" title="tip" class="tooltip-item">Tooltip Item</div>')
.appendTo($container)
.before('<div class="spacer"/>')
.after('<div class="spacer"/>')
.bootstrapTooltip({
placement: 'bottom auto',
viewport: '#scrollable-div'
})
$('#scrollable-div').scrollTop(200)
$target.bootstrapTooltip('show')
assert.ok($('.tooltip').is('.fade.bottom.in'), 'has correct classes applied')
$target.bootstrapTooltip('hide')
assert.strictEqual($('.tooltip').length, 0, 'tooltip removed from dom')
$styles.remove()
})
QUnit.test('should display the tip on top whenever scrollable viewport doesn\'t have enough room if the given placement is "auto bottom"', function (assert) {
assert.expect(2)
var styles = '<style>'
+ '#scrollable-div { height: 200px; overflow: auto; }'
+ '.tooltip-item { margin-top: 400px; width: 150px; }'
+ '</style>'
var $styles = $(styles).appendTo('head')
var $container = $('<div id="scrollable-div"/>').appendTo('#qunit-fixture')
var $target = $('<div rel="tooltip" title="tip" class="tooltip-item">Tooltip Item</div>')
.appendTo($container)
.bootstrapTooltip({
placement: 'bottom auto',
viewport: '#scrollable-div'
})
$('#scrollable-div').scrollTop(400)
$target.bootstrapTooltip('show')
assert.ok($('.tooltip').is('.fade.top.in'), 'has correct classes applied')
$target.bootstrapTooltip('hide')
assert.strictEqual($('.tooltip').length, 0, 'tooltip removed from dom')
$styles.remove()
})
QUnit.test('should adjust the tip\'s top position when up against the top of the viewport', function (assert) {
assert.expect(2)
var styles = '<style>'
+ '.tooltip .tooltip-inner { width: 200px; height: 200px; max-width: none; }'
+ 'a[rel="tooltip"] { position: fixed; }'
+ '</style>'
var $styles = $(styles).appendTo('head')
var $container = $('<div/>').appendTo('#qunit-fixture')
var $target = $('<a href="#" rel="tooltip" title="tip" style="top: 0px; left: 0px;"/>')
.appendTo($container)
.bootstrapTooltip({
placement: 'right',
viewport: {
selector: 'body',
padding: 12
}
})
$target.bootstrapTooltip('show')
assert.strictEqual(Math.round($container.find('.tooltip').offset().top), 12)
$target.bootstrapTooltip('hide')
assert.strictEqual($('.tooltip').length, 0, 'tooltip removed from dom')
$styles.remove()
})
QUnit.test('should adjust the tip\'s top position when up against the bottom of the viewport', function (assert) {
assert.expect(2)
var styles = '<style>'
+ '.tooltip .tooltip-inner { width: 200px; height: 200px; max-width: none; }'
+ 'a[rel="tooltip"] { position: fixed; }'
+ '</style>'
var $styles = $(styles).appendTo('head')
var $container = $('<div/>').appendTo('#qunit-fixture')
var $target = $('<a href="#" rel="tooltip" title="tip" style="bottom: 0px; left: 0px;"/>')
.appendTo($container)
.bootstrapTooltip({
placement: 'right',
viewport: {
selector: 'body',
padding: 12
}
})
$target.bootstrapTooltip('show')
var $tooltip = $container.find('.tooltip')
assert.strictEqual(Math.round($tooltip.offset().top), Math.round($(window).height() - 12 - $tooltip[0].offsetHeight))
$target.bootstrapTooltip('hide')
assert.strictEqual($('.tooltip').length, 0, 'tooltip removed from dom')
$container.remove()
$styles.remove()
})
QUnit.test('should adjust the tip\'s left position when up against the left of the viewport', function (assert) {
assert.expect(2)
var styles = '<style>'
+ '.tooltip .tooltip-inner { width: 200px; height: 200px; max-width: none; }'
+ 'a[rel="tooltip"] { position: fixed; }'
+ '</style>'
var $styles = $(styles).appendTo('head')
var $container = $('<div/>').appendTo('#qunit-fixture')
var $target = $('<a href="#" rel="tooltip" title="tip" style="top: 0px; left: 0px;"/>')
.appendTo($container)
.bootstrapTooltip({
placement: 'bottom',
viewport: {
selector: 'body',
padding: 12
}
})
$target.bootstrapTooltip('show')
assert.strictEqual(Math.round($container.find('.tooltip').offset().left), 12)
$target.bootstrapTooltip('hide')
assert.strictEqual($('.tooltip').length, 0, 'tooltip removed from dom')
$container.remove()
$styles.remove()
})
QUnit.test('should adjust the tip\'s left position when up against the right of the viewport', function (assert) {
assert.expect(2)
var styles = '<style>'
+ '.tooltip .tooltip-inner { width: 200px; height: 200px; max-width: none; }'
+ 'a[rel="tooltip"] { position: fixed; }'
+ '</style>'
var $styles = $(styles).appendTo('head')
var $container = $('<div/>').appendTo('body')
var $target = $('<a href="#" rel="tooltip" title="tip" style="top: 0px; right: 0px;"/>')
.appendTo($container)
.bootstrapTooltip({
placement: 'bottom',
viewport: {
selector: 'body',
padding: 12
}
})
$target.bootstrapTooltip('show')
var $tooltip = $container.find('.tooltip')
assert.strictEqual(Math.round($tooltip.offset().left), Math.round($(window).width() - 12 - $tooltip[0].offsetWidth))
$target.bootstrapTooltip('hide')
assert.strictEqual($('.tooltip').length, 0, 'tooltip removed from dom')
$container.remove()
$styles.remove()
})
QUnit.test('should adjust the tip when up against the right of an arbitrary viewport', function (assert) {
assert.expect(2)
var styles = '<style>'
+ '.tooltip, .tooltip .tooltip-inner { width: 200px; height: 200px; max-width: none; }'
+ '.container-viewport { position: absolute; top: 50px; left: 60px; width: 300px; height: 300px; }'
+ 'a[rel="tooltip"] { position: fixed; }'
+ '</style>'
var $styles = $(styles).appendTo('head')
var $container = $('<div class="container-viewport"/>').appendTo(document.body)
var $target = $('<a href="#" rel="tooltip" title="tip" style="top: 50px; left: 350px;"/>')
.appendTo($container)
.bootstrapTooltip({
placement: 'bottom',
viewport: '.container-viewport'
})
$target.bootstrapTooltip('show')
var $tooltip = $container.find('.tooltip')
assert.strictEqual(Math.round($tooltip.offset().left), Math.round(60 + $container.width() - $tooltip[0].offsetWidth))
$target.bootstrapTooltip('hide')
assert.strictEqual($('.tooltip').length, 0, 'tooltip removed from dom')
$container.remove()
$styles.remove()
})
QUnit.test('should get viewport element from function', function (assert) {
assert.expect(3)
var styles = '<style>'
+ '.tooltip, .tooltip .tooltip-inner { width: 200px; height: 200px; max-width: none; }'
+ '.container-viewport { position: absolute; top: 50px; left: 60px; width: 300px; height: 300px; }'
+ 'a[rel="tooltip"] { position: fixed; }'
+ '</style>'
var $styles = $(styles).appendTo('head')
var $container = $('<div class="container-viewport"/>').appendTo(document.body)
var $target = $('<a href="#" rel="tooltip" title="tip" style="top: 50px; left: 350px;"/>').appendTo($container)
$target
.bootstrapTooltip({
placement: 'bottom',
viewport: function ($element) {
assert.strictEqual($element[0], $target[0], 'viewport function was passed target as argument')
return ($element.closest('.container-viewport'))
}
})
$target.bootstrapTooltip('show')
var $tooltip = $container.find('.tooltip')
assert.strictEqual(Math.round($tooltip.offset().left), Math.round(60 + $container.width() - $tooltip[0].offsetWidth))
$target.bootstrapTooltip('hide')
assert.strictEqual($('.tooltip').length, 0, 'tooltip removed from dom')
$container.remove()
$styles.remove()
})
QUnit.test('should not misplace the tip when the right edge offset is greater or equal than the viewport width', function (assert) {
assert.expect(2)
var styles = '<style>'
+ '.tooltip, .tooltip *, .tooltip *:before, .tooltip *:after { box-sizing: border-box; }'
+ '.container-viewport, .container-viewport *, .container-viewport *:before, .container-viewport *:after { box-sizing: border-box; }'
+ '.tooltip, .tooltip .tooltip-inner { width: 50px; height: 50px; max-width: none; background: red; }'
+ '.container-viewport { padding: 100px; margin-left: 100px; width: 100px; }'
+ '</style>'
var $styles = $(styles).appendTo('head')
var $container = $('<div class="container-viewport"/>').appendTo(document.body)
var $target = $('<a href="#" rel="tooltip" title="tip">foobar</a>')
.appendTo($container)
.bootstrapTooltip({
viewport: '.container-viewport'
})
$target.bootstrapTooltip('show')
var $tooltip = $container.find('.tooltip')
assert.strictEqual(Math.round($tooltip.offset().left), Math.round($target.position().left + $target.width() / 2 - $tooltip[0].offsetWidth / 2))
$target.bootstrapTooltip('hide')
assert.strictEqual($('.tooltip').length, 0, 'tooltip removed from dom')
$container.remove()
$styles.remove()
})
QUnit.test('should not error when trying to show an auto-placed tooltip that has been removed from the dom', function (assert) {
QUnit.test('should not error when trying to show an top-placed tooltip that has been removed from the dom', function (assert) {
assert.expect(1)
var passed = true
var $tooltip = $('<a href="#" rel="tooltip" title="Another tooltip"/>')
@@ -821,7 +389,7 @@ $(function () {
.one('show.bs.tooltip', function () {
$(this).remove()
})
.bootstrapTooltip({ placement: 'auto' })
.bootstrapTooltip({ attachment: 'top' })
try {
$tooltip.bootstrapTooltip('show')
@@ -858,12 +426,12 @@ $(function () {
.find('a')
.css('margin-top', 200)
.bootstrapTooltip({
placement: 'top',
attachment: 'top',
animate: false
})
.bootstrapTooltip('show')
var $tooltip = $container.find('.tooltip')
var $tooltip = $($trigger.data('bs.tooltip').tip)
setTimeout(function () {
assert.ok(Math.round($tooltip.offset().top + $tooltip.outerHeight()) <= Math.round($trigger.offset().top))
@@ -871,38 +439,6 @@ $(function () {
}, 0)
})
QUnit.test('should place tooltip inside viewport', function (assert) {
assert.expect(1)
var done = assert.async()
var $container = $('<div/>')
.css({
position: 'absolute',
width: 200,
height: 200,
bottom: 0,
left: 0
})
.appendTo('#qunit-fixture')
$('<a href="#" title="Very very very very very very very very long tooltip">Hover me</a>')
.css({
position: 'absolute',
top: 0,
left: 0
})
.appendTo($container)
.bootstrapTooltip({
placement: 'top'
})
.bootstrapTooltip('show')
setTimeout(function () {
assert.ok($('.tooltip').offset().left >= 0)
done()
}, 0)
})
QUnit.test('should show tooltip if leave event hasn\'t occurred before delay expires', function (assert) {
assert.expect(2)
var done = assert.async()
@@ -1021,16 +557,16 @@ $(function () {
.bootstrapTooltip({ delay: { show: 0, hide: 150 }})
setTimeout(function () {
assert.ok($tooltip.data('bs.tooltip').$tip.is('.fade.in'), '1ms: tooltip faded in')
assert.ok($($tooltip.data('bs.tooltip').tip).is('.fade.in'), '1ms: tooltip faded in')
$tooltip.trigger('mouseout')
setTimeout(function () {
assert.ok($tooltip.data('bs.tooltip').$tip.is('.fade.in'), '100ms: tooltip still faded in')
assert.ok($($tooltip.data('bs.tooltip').tip).is('.fade.in'), '100ms: tooltip still faded in')
}, 100)
setTimeout(function () {
assert.ok(!$tooltip.data('bs.tooltip').$tip.is('.in'), '200ms: tooltip removed')
assert.ok(!$($tooltip.data('bs.tooltip').tip).is('.in'), '200ms: tooltip removed')
done()
}, 200)
@@ -1073,61 +609,19 @@ $(function () {
assert.strictEqual($('.tooltip').length, 0, 'tooltip removed from dom')
done()
})
.bootstrapTooltip({ container: 'body', placement: 'top', trigger: 'manual' })
.bootstrapTooltip({ attachment: 'top', trigger: 'manual' })
$circle.bootstrapTooltip('show')
})
QUnit.test('should correctly determine auto placement based on container rather than parent', function (assert) {
assert.expect(2)
var done = assert.async()
var styles = '<style>'
+ '.tooltip, .tooltip *, .tooltip *:before, .tooltip *:after { box-sizing: border-box; }'
+ '.tooltip { position: absolute; display: block; font-size: 12px; line-height: 1.4; }'
+ '.tooltip .tooltip-inner { max-width: 200px; padding: 3px 8px; font-family: Helvetica; text-align: center; }'
+ '#trigger-parent {'
+ ' position: fixed;'
+ ' top: 100px;'
+ ' right: 17px;'
+ '}'
+ '</style>'
var $styles = $(styles).appendTo('head')
$('#qunit-fixture').append('<span id="trigger-parent"><a id="tt-trigger" title="If a_larger_text is written here, it won\'t fit using older broken version of BS">HOVER OVER ME</a></span>')
var $trigger = $('#tt-trigger')
$trigger
.on('shown.bs.tooltip', function () {
var $tip = $('.tooltip-inner')
var tipXrightEdge = $tip.offset().left + $tip.width()
var triggerXleftEdge = $trigger.offset().left
assert.ok(tipXrightEdge < triggerXleftEdge, 'tooltip with auto left placement, when near the right edge of the viewport, gets left placement')
$trigger.bootstrapTooltip('hide')
})
.on('hidden.bs.tooltip', function () {
$styles.remove()
$(this).remove()
assert.strictEqual($('.tooltip').length, 0, 'tooltip removed from dom')
done()
})
.bootstrapTooltip({
container: 'body',
placement: 'auto left',
trigger: 'manual'
})
$trigger.bootstrapTooltip('show')
})
QUnit.test('should not reload the tooltip on subsequent mouseenter events', function (assert) {
assert.expect(1)
var titleHtml = function () {
var uid = $.fn.bootstrapTooltip.Constructor.prototype.getUID('tooltip')
var uid = Util.getUID('tooltip')
return '<p id="tt-content">' + uid + '</p><p>' + uid + '</p><p>' + uid + '</p>'
}
var $tooltip = $('<span id="tt-outer" rel="tooltip" data-trigger="hover" data-placement="top">some text</span>')
var $tooltip = $('<span id="tt-outer" rel="tooltip" data-trigger="hover" data-attachment="top">some text</span>')
.appendTo('#qunit-fixture')
$tooltip.bootstrapTooltip({
@@ -1149,12 +643,13 @@ $(function () {
QUnit.test('should not reload the tooltip if the mouse leaves and re-enters before hiding', function (assert) {
assert.expect(4)
var titleHtml = function () {
var uid = $.fn.bootstrapTooltip.Constructor.prototype.getUID('tooltip')
var uid = Util.getUID('tooltip')
return '<p id="tt-content">' + uid + '</p><p>' + uid + '</p><p>' + uid + '</p>'
}
var $tooltip = $('<span id="tt-outer" rel="tooltip" data-trigger="hover" data-placement="top">some text</span>')
var $tooltip = $('<span id="tt-outer" rel="tooltip" data-trigger="hover" data-attachment="top">some text</span>')
.appendTo('#qunit-fixture')
$tooltip.bootstrapTooltip({
@@ -1162,7 +657,6 @@ $(function () {
animation: false,
trigger: 'hover',
delay: { show: 0, hide: 500 },
container: $tooltip,
title: titleHtml
})
@@ -1175,47 +669,14 @@ $(function () {
$('#tt-outer').trigger('mouseleave')
assert.strictEqual(currentUid, $('#tt-content').text())
assert.ok(obj.hoverState == 'out', 'the tooltip hoverState should be set to "out"')
assert.ok(obj._hoverState == 'out', 'the tooltip hoverState should be set to "out"')
$('#tt-content').trigger('mouseenter')
assert.ok(obj.hoverState == 'in', 'the tooltip hoverState should be set to "in"')
$('#tt-outer').trigger('mouseenter')
assert.ok(obj._hoverState == 'in', 'the tooltip hoverState should be set to "in"')
assert.strictEqual(currentUid, $('#tt-content').text())
})
QUnit.test('should position arrow correctly when tooltip is moved to not appear offscreen', function (assert) {
assert.expect(2)
var done = assert.async()
var styles = '<style>'
+ '.tooltip, .tooltip *, .tooltip *:before, .tooltip *:after { box-sizing: border-box; }'
+ '.tooltip { position: absolute; }'
+ '.tooltip-arrow { position: absolute; width: 0; height: 0; }'
+ '.tooltip .tooltip-inner { max-width: 200px; padding: 3px 8px; }'
+ '</style>'
var $styles = $(styles).appendTo('head')
$('<a href="#" title="tooltip title" style="position: absolute; bottom: 0; right: 0;">Foobar</a>')
.appendTo('body')
.on('shown.bs.tooltip', function () {
var arrowStyles = $(this).data('bs.tooltip').$tip.find('.tooltip-arrow').attr('style')
assert.ok(/left/i.test(arrowStyles) && !/top/i.test(arrowStyles), 'arrow positioned correctly')
$(this).bootstrapTooltip('hide')
})
.on('hidden.bs.tooltip', function () {
$styles.remove()
$(this).remove()
assert.strictEqual($('.tooltip').length, 0, 'tooltip removed from dom')
done()
})
.bootstrapTooltip({
container: 'body',
placement: 'top',
trigger: 'manual'
})
.bootstrapTooltip('show')
})
QUnit.test('should correctly position tooltips on transformed elements', function (assert) {
var styleProps = document.documentElement.style
if (!('transform' in styleProps) && !('webkitTransform' in styleProps) && !('msTransform' in styleProps)) {
@@ -1247,21 +708,12 @@ $(function () {
done()
})
.bootstrapTooltip({
container: 'body',
placement: 'top',
trigger: 'manual'
})
$element.bootstrapTooltip('show')
})
QUnit.test('should throw an error when initializing tooltip on the document object without specifying a delegation selector', function (assert) {
assert.expect(1)
assert.throws(function () {
$(document).bootstrapTooltip({ title: 'What am I on?' })
}, new Error('`selector` option must be specified when initializing tooltip on the window.document object!'))
})
QUnit.test('should do nothing when an attempt is made to hide an uninitialized tooltip', function (assert) {
assert.expect(1)
@@ -1274,25 +726,15 @@ $(function () {
assert.strictEqual($tooltip.data('bs.tooltip'), undefined, 'should not initialize the tooltip')
})
QUnit.test('should throw an error when template contains multiple top-level elements', function (assert) {
assert.expect(1)
assert.throws(function () {
$('<a href="#" data-toggle="tooltip" title="Another tooltip"></a>')
.appendTo('#qunit-fixture')
.bootstrapTooltip({ template: '<div>Foo</div><div>Bar</div>' })
.bootstrapTooltip('show')
}, new Error('tooltip `template` option must consist of exactly 1 top-level element!'))
})
QUnit.test('should not remove tooltip if multiple triggers are set and one is still active', function (assert) {
assert.expect(41)
var $el = $('<button>Trigger</button>')
.appendTo('#qunit-fixture')
.bootstrapTooltip({ trigger: 'click hover focus', animation: false })
var tooltip = $el.data('bs.tooltip')
var $tooltip = tooltip.tip()
var $tooltip = $(tooltip.getTipElement())
function showingTooltip() { return $tooltip.hasClass('in') || tooltip.hoverState == 'in' }
function showingTooltip() { return $tooltip.hasClass('in') || tooltip._hoverState == 'in' }
var tests = [
['mouseenter', 'mouseleave'],

2
js/tests/vendor/tether.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -5,7 +5,7 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Tooltip</title>
<link rel="stylesheet" href="../../../dist/css/bootstrap.min.css">
<link rel="stylesheet" href="../../../dist/css/bootstrap.css">
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
@@ -22,22 +22,23 @@
<h1>Tooltip <small>Bootstrap Visual Test</small></h1>
</div>
<p class="muted" style="margin-bottom: 0;">Tight pants next level keffiyeh <a href="#" data-toggle="tooltip" title="" data-original-title="Default tooltip">you probably</a> haven't heard of them. Photo booth beard raw denim letterpress vegan messenger bag stumptown. Farm-to-table seitan, mcsweeney's fixie sustainable quinoa 8-bit american apparel <a href="#" data-toggle="tooltip" title="" data-original-title="Another tooltip">have a</a> terry richardson vinyl chambray. Beard stumptown, cardigans banh mi lomo thundercats. Tofu biodiesel williamsburg marfa, four loko mcsweeney's cleanse vegan chambray. A really ironic artisan <a href="#" data-toggle="tooltip" title="" data-original-title="Another one here too">whatever keytar</a>, scenester farm-to-table banksy Austin <a href="#" data-toggle="tooltip" title="" data-original-title="The last tip!">twitter handle</a> freegan cred raw denim single-origin coffee viral.
<p class="muted" style="margin-bottom: 0;">Tight pants next level keffiyeh <a href="#" data-toggle="tooltip" title="Default tooltip">you probably</a> haven't heard of them. Photo booth beard raw denim letterpress vegan messenger bag stumptown. Farm-to-table seitan, mcsweeney's fixie sustainable quinoa 8-bit american apparel <a href="#" data-toggle="tooltip" title="Another tooltip">have a</a> terry richardson vinyl chambray. Beard stumptown, cardigans banh mi lomo thundercats. Tofu biodiesel williamsburg marfa, four loko mcsweeney's cleanse vegan chambray. A really ironic artisan <a href="#" data-toggle="tooltip" title="Another one here too">whatever keytar</a>, scenester farm-to-table banksy Austin <a href="#" data-toggle="tooltip" title="The last tip!">twitter handle</a> freegan cred raw denim single-origin coffee viral.
</p>
<hr>
<p>
<button type="button" class="btn btn-default" data-toggle="tooltip" data-placement="left" title="" data-original-title="Tooltip on left">Tooltip on left</button>
<button type="button" class="btn btn-default" data-toggle="tooltip" data-placement="top" title="Tooltip on top">Tooltip on top</button>
<button type="button" class="btn btn-default" data-toggle="tooltip" data-placement="bottom" title="Tooltip on bottom">Tooltip on bottom</button>
<button type="button" class="btn btn-default" data-toggle="tooltip" data-placement="right" title="Tooltip on right">Tooltip on right</button>
<button type="button" class="btn btn-default" data-toggle="tooltip" data-attachment="left" title="" data-original-title="Tooltip on left">Tooltip on left</button>
<button type="button" class="btn btn-default" data-toggle="tooltip" data-attachment="top" title="Tooltip on top">Tooltip on top</button>
<button type="button" class="btn btn-default" data-toggle="tooltip" data-attachment="bottom" title="Tooltip on bottom">Tooltip on bottom</button>
<button type="button" class="btn btn-default" data-toggle="tooltip" data-attachment="right" title="Tooltip on right">Tooltip on right</button>
</p>
</div>
<!-- JavaScript Includes -->
<script src="../vendor/jquery.min.js"></script>
<script src="../../transition.js"></script>
<script src="../../tooltip.js"></script>
<script src="../vendor/tether.min.js"></script>
<script src="../../dist/util.js"></script>
<script src="../../dist/tooltip.js"></script>
<!-- JavaScript Test -->
<script>