Skip to content

Commit aa2c5b5

Browse files
committed
Merge pull request twbs#16014 from redbmk/issue-16008
Multiple tooltip triggers don't play well together
2 parents a19441d + 4b26903 commit aa2c5b5

File tree

2 files changed

+70
-6
lines changed

2 files changed

+70
-6
lines changed

js/tests/unit/tooltip.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1284,4 +1284,42 @@ $(function () {
12841284
}, new Error('tooltip `template` option must consist of exactly 1 top-level element!'))
12851285
})
12861286

1287+
QUnit.test('should not remove tooltip if multiple triggers are set and one is still active', function (assert) {
1288+
assert.expect(41)
1289+
var $el = $('<button>Trigger</button>')
1290+
.appendTo('#qunit-fixture')
1291+
.bootstrapTooltip({ trigger: 'click hover focus', animation: false })
1292+
var tooltip = $el.data('bs.tooltip')
1293+
var $tooltip = tooltip.tip()
1294+
1295+
function showingTooltip() { return $tooltip.hasClass('in') || tooltip.hoverState == 'in' }
1296+
1297+
var tests = [
1298+
['mouseenter', 'mouseleave'],
1299+
1300+
['focusin', 'focusout'],
1301+
1302+
['click', 'click'],
1303+
1304+
['mouseenter', 'focusin', 'focusout', 'mouseleave'],
1305+
['mouseenter', 'focusin', 'mouseleave', 'focusout'],
1306+
1307+
['focusin', 'mouseenter', 'mouseleave', 'focusout'],
1308+
['focusin', 'mouseenter', 'focusout', 'mouseleave'],
1309+
1310+
['click', 'focusin', 'mouseenter', 'focusout', 'mouseleave', 'click'],
1311+
['mouseenter', 'click', 'focusin', 'focusout', 'mouseleave', 'click'],
1312+
['mouseenter', 'focusin', 'click', 'click', 'mouseleave', 'focusout']
1313+
]
1314+
1315+
assert.ok(!showingTooltip())
1316+
1317+
$.each(tests, function (idx, triggers) {
1318+
for (var i = 0, len = triggers.length; i < len; i++) {
1319+
$el.trigger(triggers[i]);
1320+
assert.equal(i < (len - 1), showingTooltip())
1321+
}
1322+
})
1323+
})
1324+
12871325
})

js/tooltip.js

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
this.timeout = null
2222
this.hoverState = null
2323
this.$element = null
24+
this.inState = null
2425

2526
this.init('tooltip', element, options)
2627
}
@@ -51,6 +52,7 @@
5152
this.$element = $(element)
5253
this.options = this.getOptions(options)
5354
this.$viewport = this.options.viewport && $($.isFunction(this.options.viewport) ? this.options.viewport.call(this, this.$element) : (this.options.viewport.selector || this.options.viewport))
55+
this.inState = { click: false, hover: false, focus: false }
5456

5557
if (this.$element[0] instanceof document.constructor && !this.options.selector) {
5658
throw new Error('`selector` option must be specified when initializing ' + this.type + ' on the window.document object!')
@@ -109,16 +111,20 @@
109111
var self = obj instanceof this.constructor ?
110112
obj : $(obj.currentTarget).data('bs.' + this.type)
111113

112-
if (self && self.$tip && self.$tip.is(':visible')) {
113-
self.hoverState = 'in'
114-
return
115-
}
116-
117114
if (!self) {
118115
self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
119116
$(obj.currentTarget).data('bs.' + this.type, self)
120117
}
121118

119+
if (obj instanceof $.Event) {
120+
self.inState[obj.type == 'focusin' ? 'focus' : 'hover'] = true
121+
}
122+
123+
if (self.tip().hasClass('in') || self.hoverState == 'in') {
124+
self.hoverState = 'in'
125+
return
126+
}
127+
122128
clearTimeout(self.timeout)
123129

124130
self.hoverState = 'in'
@@ -130,6 +136,14 @@
130136
}, self.options.delay.show)
131137
}
132138

139+
Tooltip.prototype.isInStateTrue = function () {
140+
for (var key in this.inState) {
141+
if (this.inState[key]) return true
142+
}
143+
144+
return false
145+
}
146+
133147
Tooltip.prototype.leave = function (obj) {
134148
var self = obj instanceof this.constructor ?
135149
obj : $(obj.currentTarget).data('bs.' + this.type)
@@ -139,6 +153,12 @@
139153
$(obj.currentTarget).data('bs.' + this.type, self)
140154
}
141155

156+
if (obj instanceof $.Event) {
157+
self.inState[obj.type == 'focusout' ? 'focus' : 'hover'] = false
158+
}
159+
160+
if (self.isInStateTrue()) return
161+
142162
clearTimeout(self.timeout)
143163

144164
self.hoverState = 'out'
@@ -438,7 +458,13 @@
438458
}
439459
}
440460

441-
self.tip().hasClass('in') ? self.leave(self) : self.enter(self)
461+
if (e) {
462+
self.inState.click = !self.inState.click
463+
if (self.isInStateTrue()) self.enter(self)
464+
else self.leave(self)
465+
} else {
466+
self.tip().hasClass('in') ? self.leave(self) : self.enter(self)
467+
}
442468
}
443469

444470
Tooltip.prototype.destroy = function () {

0 commit comments

Comments
 (0)