diff --git a/docs/4.0/components/dropdowns.md b/docs/4.0/components/dropdowns.md
index cde123b00c41..926729a3d9ab 100644
--- a/docs/4.0/components/dropdowns.md
+++ b/docs/4.0/components/dropdowns.md
@@ -723,9 +723,17 @@ Options can be passed via data attributes or JavaScript. For data attributes, ap
true |
Allow Dropdown to flip in case of an overlapping on the reference element. For more information refer to Popper.js's flip docs. |
+
+ boundary |
+ string | element |
+ 'scrollParent' |
+ Overflow constraint boundary of the dropdown menu. Accepts the values of 'viewport' , 'window' , 'scrollParent' , or an HTMLElement reference (JavaScript only). For more information refer to Popper.js's preventOverflow docs. |
+
+Note when `boundary` is set to any value other than `'scrollParent'`, the style `position: static` is applied to the `.dropdown` container.
+
### Methods
| Method | Description |
diff --git a/js/src/dropdown.js b/js/src/dropdown.js
index 8affedc6ce9e..56559b434576 100644
--- a/js/src/dropdown.js
+++ b/js/src/dropdown.js
@@ -50,7 +50,8 @@ const Dropdown = (($) => {
DROPRIGHT : 'dropright',
DROPLEFT : 'dropleft',
MENURIGHT : 'dropdown-menu-right',
- MENULEFT : 'dropdown-menu-left'
+ MENULEFT : 'dropdown-menu-left',
+ POSITION_STATIC : 'position-static'
}
const Selector = {
@@ -74,12 +75,14 @@ const Dropdown = (($) => {
const Default = {
offset : 0,
- flip : true
+ flip : true,
+ boundary : 'scrollParent'
}
const DefaultType = {
offset : '(number|string|function)',
- flip : 'boolean'
+ flip : 'boolean',
+ boundary : '(string|element)'
}
@@ -159,6 +162,12 @@ const Dropdown = (($) => {
element = parent
}
}
+ // If boundary is not `scrollParent`, then set position to `static`
+ // to allow the menu to "escape" the scroll parent's boundaries
+ // https://github.com/twbs/bootstrap/issues/24251
+ if (this._config.boundary !== 'scrollParent') {
+ $(parent).addClass(ClassName.POSITION_STATIC)
+ }
this._popper = new Popper(element, this._menu, this._getPopperConfig())
}
@@ -276,6 +285,9 @@ const Dropdown = (($) => {
offset : offsetConf,
flip : {
enabled : this._config.flip
+ },
+ preventOverflow : {
+ boundariesElement : this._config.boundary
}
}
}
diff --git a/js/tests/unit/dropdown.js b/js/tests/unit/dropdown.js
index c1202266b13d..41c53a98ad84 100644
--- a/js/tests/unit/dropdown.js
+++ b/js/tests/unit/dropdown.js
@@ -67,6 +67,50 @@ $(function () {
$dropdown.trigger($.Event('click'))
})
+ QUnit.test('should not add class position-static to dropdown if boundary not set', function (assert) {
+ assert.expect(1)
+ var done = assert.async()
+ var dropdownHTML = ''
+ var $dropdown = $(dropdownHTML).find('[data-toggle="dropdown"]').bootstrapDropdown()
+ $dropdown
+ .parent('.dropdown')
+ .on('shown.bs.dropdown', function () {
+ assert.ok(!$dropdown.parent('.dropdown').hasClass('position-static'), '"position-static" class not added')
+ done()
+ })
+ $dropdown.trigger('click')
+ })
+
+ QUnit.test('should add class position-static to dropdown if boundary not scrollParent', function (assert) {
+ assert.expect(1)
+ var done = assert.async()
+ var dropdownHTML = ''
+ var $dropdown = $(dropdownHTML).find('[data-toggle="dropdown"]').bootstrapDropdown()
+ $dropdown
+ .parent('.dropdown')
+ .on('shown.bs.dropdown', function () {
+ assert.ok($dropdown.parent('.dropdown').hasClass('position-static'), '"position-static" class added')
+ done()
+ })
+ $dropdown.trigger('click')
+ })
+
QUnit.test('should set aria-expanded="true" on target when dropdown menu is shown', function (assert) {
assert.expect(1)
var done = assert.async()