mirror of
https://github.com/twbs/bootstrap.git
synced 2025-02-25 12:22:50 +01:00
Merge pull request #12328 from benogle/bo-tooltip-adjust
Add tooltip `viewport` option, respect bounds of the viewport
This commit is contained in:
commit
2b27006c71
@ -134,6 +134,14 @@ $('#example').tooltip(options)
|
|||||||
<p>Appends the tooltip to a specific element. Example: <code>container: 'body'</code></p>
|
<p>Appends the tooltip to a specific element. Example: <code>container: 'body'</code></p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>viewport</td>
|
||||||
|
<td>string | object</td>
|
||||||
|
<td>{ selector: 'body', padding: 0 }</td>
|
||||||
|
<td>
|
||||||
|
<p>Keeps the tooltip within the bounds of this element. Example: <code>viewport: '#viewport'</code> or <code>{ selector: '#viewport', padding: 0 }</code></p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div><!-- /.table-responsive -->
|
</div><!-- /.table-responsive -->
|
||||||
|
103
examples/tooltips/viewport.html
Normal file
103
examples/tooltips/viewport.html
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta name="description" content="">
|
||||||
|
<meta name="author" content="">
|
||||||
|
<link rel="shortcut icon" href="../../docs-assets/ico/favicon.png">
|
||||||
|
|
||||||
|
<title>Tooltip Viewport Example for Bootstrap</title>
|
||||||
|
|
||||||
|
<!-- Bootstrap core CSS -->
|
||||||
|
<link href="../../dist/css/bootstrap.css" rel="stylesheet">
|
||||||
|
|
||||||
|
<!-- Just for debugging purposes. Don't actually copy this line! -->
|
||||||
|
<!--[if lt IE 9]><script src="../../docs-assets/js/ie8-responsive-file-warning.js"></script><![endif]-->
|
||||||
|
|
||||||
|
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
||||||
|
<!--[if lt IE 9]>
|
||||||
|
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
|
||||||
|
<script src="https://oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
|
||||||
|
<![endif]-->
|
||||||
|
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
height: 1200px;
|
||||||
|
}
|
||||||
|
.tooltip {
|
||||||
|
min-width: 250px;
|
||||||
|
max-width: 500px;
|
||||||
|
}
|
||||||
|
.tooltip .tooltip-inner {
|
||||||
|
min-height: 200px;
|
||||||
|
min-width: 250px;
|
||||||
|
max-width: 500px;
|
||||||
|
}
|
||||||
|
.placeholder {
|
||||||
|
height: 900px;
|
||||||
|
}
|
||||||
|
.container-viewport {
|
||||||
|
position: absolute;
|
||||||
|
left: 200px;
|
||||||
|
top: 600px;
|
||||||
|
width: 600px;
|
||||||
|
height: 400px;
|
||||||
|
background: #c00;
|
||||||
|
}
|
||||||
|
.btn-bottom {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<button class="btn pull-right tooltip-bottom", title="This should be shifted to the left">Shift Left</button>
|
||||||
|
<button class="btn tooltip-bottom", title="This should be shifted to the right">Shift Right</button>
|
||||||
|
<button class="btn tooltip-right", title="This should be shifted down">Shift Down</button>
|
||||||
|
|
||||||
|
<div class="placeholder">There is a button down there ↓</div>
|
||||||
|
|
||||||
|
<button class="btn tooltip-right", title="This should be shifted up">Shift Up</button>
|
||||||
|
|
||||||
|
<div class="container-viewport">
|
||||||
|
<button class="btn tooltip-viewport-bottom", title="This should be shifted Left">Shift Left</button>
|
||||||
|
<button class="btn tooltip-viewport-right", title="This should be shifted Down">Shift Down</button>
|
||||||
|
|
||||||
|
<button class="btn pull-right tooltip-viewport-bottom", title="This should be shifted Right">Shift Right</button>
|
||||||
|
|
||||||
|
<button class="btn tooltip-viewport-right btn-bottom", title="This should be shifted up">Shift Up</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Bootstrap core JavaScript
|
||||||
|
================================================== -->
|
||||||
|
<!-- Placed at the end of the document so the pages load faster -->
|
||||||
|
<script src="https://code.jquery.com/jquery-1.10.2.min.js"></script>
|
||||||
|
<script src="../../js/tooltip.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(function(){
|
||||||
|
$('.tooltip-right').tooltip({
|
||||||
|
placement: 'right',
|
||||||
|
viewport: {selector: 'body', padding: 2}
|
||||||
|
});
|
||||||
|
$('.tooltip-bottom').tooltip({
|
||||||
|
placement: 'bottom',
|
||||||
|
viewport: {selector: 'body', padding: 2}
|
||||||
|
});
|
||||||
|
$('.tooltip-viewport-right').tooltip({
|
||||||
|
placement: 'right',
|
||||||
|
viewport: {selector: '.container-viewport', padding: 2}
|
||||||
|
});
|
||||||
|
$('.tooltip-viewport-bottom').tooltip({
|
||||||
|
placement: 'bottom',
|
||||||
|
viewport: {selector: '.container-viewport', padding: 2}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -337,12 +337,12 @@ $(function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('should add position class before positioning so that position-specific styles are taken into account', function () {
|
test('should add position class before positioning so that position-specific styles are taken into account', function () {
|
||||||
$('head').append('<style> .tooltip.right { white-space: nowrap; } .tooltip.right .tooltip-inner { max-width: none; } </style>')
|
$('head').append('<style id="test"> .tooltip.right { white-space: nowrap; } .tooltip.right .tooltip-inner { max-width: none; } </style>')
|
||||||
|
|
||||||
var container = $('<div />').appendTo('body'),
|
var container = $('<div />').appendTo('body'),
|
||||||
target = $('<a href="#" rel="tooltip" title="very very very very very very very very long tooltip in one line"></a>')
|
target = $('<a href="#" rel="tooltip" title="very very very very very very very very long tooltip in one line"></a>')
|
||||||
.appendTo(container)
|
.appendTo(container)
|
||||||
.tooltip({placement: 'right'})
|
.tooltip({placement: 'right', viewport: null})
|
||||||
.tooltip('show'),
|
.tooltip('show'),
|
||||||
tooltip = container.find('.tooltip')
|
tooltip = container.find('.tooltip')
|
||||||
|
|
||||||
@ -352,6 +352,7 @@ $(function () {
|
|||||||
var topDiff = top - top2
|
var topDiff = top - top2
|
||||||
ok(topDiff <= 1 && topDiff >= -1)
|
ok(topDiff <= 1 && topDiff >= -1)
|
||||||
target.tooltip('hide')
|
target.tooltip('hide')
|
||||||
|
$('head #test').remove()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('tooltip title test #1', function () {
|
test('tooltip title test #1', function () {
|
||||||
@ -428,4 +429,80 @@ $(function () {
|
|||||||
ttContainer.remove()
|
ttContainer.remove()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('should adjust the tip\'s top when up against the top of the viewport', function () {
|
||||||
|
$('head').append('<style id="test"> .tooltip .tooltip-inner { width: 200px; height: 200px; max-width: none; } </style>')
|
||||||
|
|
||||||
|
var container = $('<div />').appendTo('body'),
|
||||||
|
target = $('<a href="#" rel="tooltip" title="tip" style="position: fixed; top: 0px; left: 0px;"></a>')
|
||||||
|
.appendTo(container)
|
||||||
|
.tooltip({placement: 'right', viewport: {selector: 'body', padding: 12}})
|
||||||
|
.tooltip('show'),
|
||||||
|
tooltip = container.find('.tooltip')
|
||||||
|
|
||||||
|
ok( Math.round(tooltip.offset().top) === 12 )
|
||||||
|
target.tooltip('hide')
|
||||||
|
$('head #test').remove()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should adjust the tip\'s top when up against the bottom of the viewport', function () {
|
||||||
|
$('head').append('<style id="test"> .tooltip .tooltip-inner { width: 200px; height: 200px; max-width: none; } </style>')
|
||||||
|
|
||||||
|
var container = $('<div />').appendTo('body'),
|
||||||
|
target = $('<a href="#" rel="tooltip" title="tip" style="position: fixed; bottom: 0px; left: 0px;"></a>')
|
||||||
|
.appendTo(container)
|
||||||
|
.tooltip({placement: 'right', viewport: {selector: 'body', padding: 12}})
|
||||||
|
.tooltip('show'),
|
||||||
|
tooltip = container.find('.tooltip')
|
||||||
|
|
||||||
|
ok( Math.round(tooltip.offset().top) === Math.round($(window).height() - 12 - tooltip[0].offsetHeight) )
|
||||||
|
target.tooltip('hide')
|
||||||
|
$('head #test').remove()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should adjust the tip\'s left when up against the left of the viewport', function () {
|
||||||
|
$('head').append('<style id="test"> .tooltip .tooltip-inner { width: 200px; height: 200px; max-width: none; } </style>')
|
||||||
|
|
||||||
|
var container = $('<div />').appendTo('body'),
|
||||||
|
target = $('<a href="#" rel="tooltip" title="tip" style="position: fixed; top: 0px; left: 0px;"></a>')
|
||||||
|
.appendTo(container)
|
||||||
|
.tooltip({placement: 'bottom', viewport: {selector: 'body', padding: 12}})
|
||||||
|
.tooltip('show'),
|
||||||
|
tooltip = container.find('.tooltip')
|
||||||
|
|
||||||
|
ok( Math.round(tooltip.offset().left) === 12 )
|
||||||
|
target.tooltip('hide')
|
||||||
|
$('head #test').remove()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should adjust the tip\'s left when up against the right of the viewport', function () {
|
||||||
|
$('head').append('<style id="test"> .tooltip .tooltip-inner { width: 200px; height: 200px; max-width: none; } </style>')
|
||||||
|
|
||||||
|
var container = $('<div />').appendTo('body'),
|
||||||
|
target = $('<a href="#" rel="tooltip" title="tip" style="position: fixed; top: 0px; right: 0px;"></a>')
|
||||||
|
.appendTo(container)
|
||||||
|
.tooltip({placement: 'bottom', viewport: {selector: 'body', padding: 12}})
|
||||||
|
.tooltip('show'),
|
||||||
|
tooltip = container.find('.tooltip')
|
||||||
|
|
||||||
|
ok( Math.round(tooltip.offset().left) === Math.round($(window).width() - 12 - tooltip[0].offsetWidth) )
|
||||||
|
target.tooltip('hide')
|
||||||
|
$('head #test').remove()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should adjust the tip when up against the right of an arbitrary viewport', function () {
|
||||||
|
$('head').append('<style id="test"> .tooltip, .tooltip .tooltip-inner { width: 200px; height: 200px; max-width: none; } </style>')
|
||||||
|
$('head').append('<style id="viewport-style"> .container-viewport { position: absolute; top: 50px; left: 60px; width: 300px; height: 300px; } </style>')
|
||||||
|
|
||||||
|
var container = $('<div />', {class: 'container-viewport'}).appendTo('body'),
|
||||||
|
target = $('<a href="#" rel="tooltip" title="tip" style="position: fixed; top: 50px; left: 350px;"></a>')
|
||||||
|
.appendTo(container)
|
||||||
|
.tooltip({placement: 'bottom', viewport: '.container-viewport'})
|
||||||
|
.tooltip('show'),
|
||||||
|
tooltip = container.find('.tooltip')
|
||||||
|
|
||||||
|
ok( Math.round(tooltip.offset().left) === Math.round(60 + container.width() - tooltip[0].offsetWidth) )
|
||||||
|
target.tooltip('hide')
|
||||||
|
$('head #test').remove()
|
||||||
|
$('head #viewport-style').remove()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
100
js/tooltip.js
100
js/tooltip.js
@ -34,14 +34,19 @@
|
|||||||
title: '',
|
title: '',
|
||||||
delay: 0,
|
delay: 0,
|
||||||
html: false,
|
html: false,
|
||||||
container: false
|
container: false,
|
||||||
|
viewport: {
|
||||||
|
selector: 'body',
|
||||||
|
padding: 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Tooltip.prototype.init = function (type, element, options) {
|
Tooltip.prototype.init = function (type, element, options) {
|
||||||
this.enabled = true
|
this.enabled = true
|
||||||
this.type = type
|
this.type = type
|
||||||
this.$element = $(element)
|
this.$element = $(element)
|
||||||
this.options = this.getOptions(options)
|
this.options = this.getOptions(options)
|
||||||
|
this.$viewport = this.options.viewport && $(this.options.viewport.selector || this.options.viewport)
|
||||||
|
|
||||||
var triggers = this.options.trigger.split(' ')
|
var triggers = this.options.trigger.split(' ')
|
||||||
|
|
||||||
@ -157,18 +162,14 @@
|
|||||||
var actualHeight = $tip[0].offsetHeight
|
var actualHeight = $tip[0].offsetHeight
|
||||||
|
|
||||||
if (autoPlace) {
|
if (autoPlace) {
|
||||||
var $parent = this.$element.parent()
|
|
||||||
|
|
||||||
var orgPlacement = placement
|
var orgPlacement = placement
|
||||||
var docScroll = document.documentElement.scrollTop
|
var $parent = this.$element.parent()
|
||||||
var parentWidth = this.options.container == 'body' ? window.innerWidth : $parent.outerWidth()
|
var parentDim = this.getPosition($parent)
|
||||||
var parentHeight = this.options.container == 'body' ? window.innerHeight : $parent.outerHeight()
|
|
||||||
var parentLeft = this.options.container == 'body' ? 0 : $parent.offset().left
|
|
||||||
|
|
||||||
placement = placement == 'bottom' && pos.top + pos.height + actualHeight - docScroll > parentHeight ? 'top' :
|
placement = placement == 'bottom' && pos.top + pos.height + actualHeight - parentDim.scroll > parentDim.height ? 'top' :
|
||||||
placement == 'top' && pos.top - docScroll - actualHeight < 0 ? 'bottom' :
|
placement == 'top' && pos.top - parentDim.scroll - actualHeight < 0 ? 'bottom' :
|
||||||
placement == 'right' && pos.right + actualWidth > parentWidth ? 'left' :
|
placement == 'right' && pos.right + actualWidth > parentDim.width ? 'left' :
|
||||||
placement == 'left' && pos.left - actualWidth < parentLeft ? 'right' :
|
placement == 'left' && pos.left - actualWidth < parentDim.left ? 'right' :
|
||||||
placement
|
placement
|
||||||
|
|
||||||
$tip
|
$tip
|
||||||
@ -228,29 +229,20 @@
|
|||||||
var actualHeight = $tip[0].offsetHeight
|
var actualHeight = $tip[0].offsetHeight
|
||||||
|
|
||||||
if (placement == 'top' && actualHeight != height) {
|
if (placement == 'top' && actualHeight != height) {
|
||||||
replace = true
|
|
||||||
offset.top = offset.top + height - actualHeight
|
offset.top = offset.top + height - actualHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
if (/bottom|top/.test(placement)) {
|
var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight)
|
||||||
var delta = 0
|
|
||||||
|
|
||||||
if (offset.left < 0) {
|
if (delta.left) offset.left += delta.left
|
||||||
delta = offset.left * -2
|
else offset.top += delta.top
|
||||||
offset.left = 0
|
|
||||||
|
|
||||||
$tip.offset(offset)
|
var arrowDelta = delta.left ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight
|
||||||
|
var arrowPosition = delta.left ? 'left' : 'top'
|
||||||
|
var arrowOffsetPosition = delta.left ? 'offsetWidth' : 'offsetHeight'
|
||||||
|
|
||||||
actualWidth = $tip[0].offsetWidth
|
$tip.offset(offset)
|
||||||
actualHeight = $tip[0].offsetHeight
|
this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], arrowPosition)
|
||||||
}
|
|
||||||
|
|
||||||
this.replaceArrow(delta - width + actualWidth, actualWidth, 'left')
|
|
||||||
} else {
|
|
||||||
this.replaceArrow(actualHeight - height, actualHeight, 'top')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (replace) $tip.offset(offset)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Tooltip.prototype.replaceArrow = function (delta, dimension, position) {
|
Tooltip.prototype.replaceArrow = function (delta, dimension, position) {
|
||||||
@ -303,12 +295,15 @@
|
|||||||
return this.getTitle()
|
return this.getTitle()
|
||||||
}
|
}
|
||||||
|
|
||||||
Tooltip.prototype.getPosition = function () {
|
Tooltip.prototype.getPosition = function ($element) {
|
||||||
var el = this.$element[0]
|
$element = $element || this.$element
|
||||||
return $.extend({}, (typeof el.getBoundingClientRect == 'function') ? el.getBoundingClientRect() : {
|
var el = $element[0]
|
||||||
width: el.offsetWidth,
|
var isBody = el.tagName == 'BODY'
|
||||||
height: el.offsetHeight
|
return $.extend({}, (typeof el.getBoundingClientRect == 'function') ? el.getBoundingClientRect() : null, {
|
||||||
}, this.$element.offset())
|
scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop(),
|
||||||
|
width: isBody ? $(window).width() : $element.outerWidth(),
|
||||||
|
height: isBody ? $(window).height() : $element.outerHeight()
|
||||||
|
}, isBody ? {top: 0, left: 0} : $element.offset())
|
||||||
}
|
}
|
||||||
|
|
||||||
Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {
|
Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {
|
||||||
@ -316,6 +311,35 @@
|
|||||||
placement == 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } :
|
placement == 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } :
|
||||||
placement == 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :
|
placement == 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :
|
||||||
/* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width }
|
/* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) {
|
||||||
|
var delta = { top: 0, left: 0 }
|
||||||
|
if (!this.$viewport) return delta
|
||||||
|
|
||||||
|
var viewportPadding = this.options.viewport && this.options.viewport.padding || 0
|
||||||
|
var viewportDimensions = this.getPosition(this.$viewport)
|
||||||
|
|
||||||
|
if (/right|left/.test(placement)) {
|
||||||
|
var topEdgeOffset = pos.top - viewportPadding - viewportDimensions.scroll
|
||||||
|
var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight
|
||||||
|
if (topEdgeOffset < viewportDimensions.top) { // top overflow
|
||||||
|
delta.top = viewportDimensions.top - topEdgeOffset
|
||||||
|
} else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow
|
||||||
|
delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var leftEdgeOffset = pos.left - viewportPadding
|
||||||
|
var rightEdgeOffset = pos.left + viewportPadding + actualWidth
|
||||||
|
if (leftEdgeOffset < viewportDimensions.left) { // left overflow
|
||||||
|
delta.left = viewportDimensions.left - leftEdgeOffset
|
||||||
|
} else if (rightEdgeOffset > viewportDimensions.width) { // right overflow
|
||||||
|
delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return delta
|
||||||
}
|
}
|
||||||
|
|
||||||
Tooltip.prototype.getTitle = function () {
|
Tooltip.prototype.getTitle = function () {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user