diff --git a/docs/README.md b/docs/README.md
index 45701c3..b44e134 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -37,6 +37,7 @@ Open `http://localhost:3000/` after startup. The examples are available from
the docs UI and directly at:
- `http://localhost:3000/examples/basic.html`
+- `http://localhost:3000/examples/card-header.html`
- `http://localhost:3000/examples/live-search.html`
- `http://localhost:3000/examples/multiple.html`
diff --git a/docs/content/examples.mdx b/docs/content/examples.mdx
index a213468..09dacb2 100644
--- a/docs/content/examples.mdx
+++ b/docs/content/examples.mdx
@@ -21,6 +21,7 @@ The main examples now live directly on this docs page so they inherit the docs t
- [Search and multi-select workflows](#search-and-multi-select-workflows)
- [Selection text and summaries](#selection-text-and-summaries)
- [Styling and layout](#styling-and-layout)
+ - [Bootstrap card with header](#bootstrap-card-with-header)
- [Native Bootstrap comparison](#native-bootstrap-comparison)
- [Rich option content](#rich-option-content)
- [Menu behavior](#menu-behavior)
@@ -546,6 +547,70 @@ The supported values are:
---
+### Bootstrap card with header
+
+Use this layout to verify how the picker behaves when it sits directly below a Bootstrap `.card-header`. The menu is appended to `
` so it renders outside the card, and auto-dropup is disabled so it does not cover the card header.
+
+
+
+
+ Question pool
+ Choose which questions are available for this interview.
+
+ Draft
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `}
+/>
+
+For isolated testing outside the docs page, open the standalone page:
+
+
+
+ Open the Bootstrap card header example ↗
+
+
+
### Native Bootstrap comparison
`bootstrap-select` is styled to mirror a native Bootstrap 5.3 `.form-select`: the
diff --git a/docs/content/options.mdx b/docs/content/options.mdx
index 27d30fe..4bd1fa5 100644
--- a/docs/content/options.mdx
+++ b/docs/content/options.mdx
@@ -344,6 +344,15 @@ For security reasons, the `sanitize`, `sanitizeFn`, and `whiteList` options cann
When set to false, the picker follows the width of its container.
+
+
container
+
string | element | false
+
false
+
+
Appends the dropdown menu to another element, such as 'body', instead of keeping it inside the picker wrapper.
+
Use this when the picker sits inside constrained UI such as a card or another element with clipping or stacking styles.
+
+
sanitize
boolean
@@ -374,7 +383,7 @@ For security reasons, the `sanitize`, `sanitizeFn`, and `whiteList` options cann
/>
:::info Bootstrap 5 runtime defaults
-This Bootstrap 5 build no longer supports the legacy `container`, `mobile`, `styleBase`, or `windowPadding` options. When `width` is not set, the picker follows normal Bootstrap sizing and fills its container by default.
+This Bootstrap 5 build no longer supports the legacy `mobile`, `styleBase`, or `windowPadding` options. When `width` is not set, the picker follows normal Bootstrap sizing and fills its container by default.
:::
## Tags-style live search and open options
diff --git a/docs/static/examples/card-header.html b/docs/static/examples/card-header.html
new file mode 100644
index 0000000..da9323e
--- /dev/null
+++ b/docs/static/examples/card-header.html
@@ -0,0 +1,68 @@
+
+
+
+
+
+ bootstrap-select card header example
+
+
+
+
+
+
Bootstrap card header example
+
Use this page to verify the picker when it is rendered directly below a Bootstrap card header.
+
+
+
+
+ Question pool
+ Choose which questions are available for this interview.
+
+ Draft
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/js/bootstrap-select.api.js b/js/bootstrap-select.api.js
index 1e3472d..1ff601c 100644
--- a/js/bootstrap-select.api.js
+++ b/js/bootstrap-select.api.js
@@ -497,6 +497,7 @@ Selectpicker.DEFAULTS = {
showContent: true,
dropupAuto: true,
header: false,
+ container: false,
liveSearch: false,
liveSearchPlaceholder: null,
liveSearchNormalize: false,
diff --git a/js/bootstrap-select.class.js b/js/bootstrap-select.class.js
index c2e487e..a0185d4 100644
--- a/js/bootstrap-select.class.js
+++ b/js/bootstrap-select.class.js
@@ -161,7 +161,7 @@ class Selectpicker {
this.clickListener();
var Dropdown = getDropdown();
- this.dropdown = new Dropdown(this.button);
+ this.dropdown = new Dropdown(this.button, this.options.container ? { display: 'static' } : undefined);
// store a reference to the instance for delegated handlers
this.newElement.bootstrapSelectInstance = this;
@@ -176,6 +176,11 @@ class Selectpicker {
this.setStyle();
this.setWidth();
+
+ if (this.options.container) {
+ this.selectPosition();
+ }
+
this._on(this.element, 'hide' + EVENT_KEY, function () {
if (that.isVirtual()) {
// empty menu on close
diff --git a/js/bootstrap-select.constants.js b/js/bootstrap-select.constants.js
index 5bb6e45..e24d48f 100644
--- a/js/bootstrap-select.constants.js
+++ b/js/bootstrap-select.constants.js
@@ -294,7 +294,7 @@ var changedArguments = null;
// shared flag for spacebar selection handling (mirrors original document data flag)
var spaceSelectFlag = false;
-var REMOVED_OPTIONS = ['container', 'display', 'mobile', 'styleBase', 'windowPadding'];
+var REMOVED_OPTIONS = ['display', 'mobile', 'styleBase', 'windowPadding'];
function stripRemovedOptions (source) {
if (!source || typeof source !== 'object') return source;
diff --git a/js/bootstrap-select.sizing.js b/js/bootstrap-select.sizing.js
index 49509fc..eaf2895 100644
--- a/js/bootstrap-select.sizing.js
+++ b/js/bootstrap-select.sizing.js
@@ -158,8 +158,15 @@
if (this.options.dropupAuto) {
// Get the estimated height of the menu without scrollbars.
estimate = liHeight * this.selectpicker.current.data.length + menuPadding.vert;
+ // Prefer opening downward whenever the menu can still show its controls
+ // and at least one option below the trigger. Flipping upward too early
+ // covers preceding form content such as card headers.
+ var minimumDropdownSpace = liHeight + headerHeight + searchHeight + actionsHeight + doneButtonHeight + menuPadding.vert;
+ var hasMeaningfulSpaceBelow = this.sizeInfo.selectOffsetBot >= minimumDropdownSpace;
- isDropup = this.sizeInfo.selectOffsetTop - this.sizeInfo.selectOffsetBot > this.sizeInfo.menuExtras.vert && estimate + this.sizeInfo.menuExtras.vert + 50 > this.sizeInfo.selectOffsetBot;
+ isDropup = !hasMeaningfulSpaceBelow &&
+ this.sizeInfo.selectOffsetTop - this.sizeInfo.selectOffsetBot > this.sizeInfo.menuExtras.vert &&
+ estimate + this.sizeInfo.menuExtras.vert + 50 > this.sizeInfo.selectOffsetBot;
// ensure dropup doesn't change while searching (so menu doesn't bounce back and forth)
if (this.selectpicker.isSearching === true) {
@@ -270,15 +277,15 @@
this.bsContainer = createFromHTML('');
var that = this,
- container = resolveContainer(this.options.container),
+ container = resolveContainer(this.options.container) || document.body,
pos,
containerPos,
actualHeight,
getPlacement = function (element) {
var Dropdown = getDropdown(),
containerPosition = {},
- // fall back to dropdown's default display setting if display is not manually set
- display = that.options.display || (Dropdown.Default ? Dropdown.Default.display : false);
+ // relocated menus are positioned by bootstrap-select's container wrapper
+ display = that.dropdown && that.dropdown._config ? that.dropdown._config.display : (Dropdown.Default ? Dropdown.Default.display : false);
var extraClass = element.getAttribute('class').replace(/form-control|fit-width/gi, '').trim();
if (extraClass) that.bsContainer.classList.add.apply(that.bsContainer.classList, extraClass.split(/\s+/));
@@ -296,10 +303,12 @@
actualHeight = element.classList.contains(classNames.DROPUP) ? 0 : element.offsetHeight;
- // Bootstrap 5 uses Popper for menu positioning
if (display === 'static') {
containerPosition.top = pos.top - containerPos.top + actualHeight;
containerPosition.left = pos.left - containerPos.left;
+ } else {
+ containerPosition.top = pos.top - containerPos.top;
+ containerPosition.left = pos.left - containerPos.left;
}
containerPosition.width = element.offsetWidth;
@@ -489,4 +498,3 @@
}
}
}
-
diff --git a/sass/bootstrap-select.scss b/sass/bootstrap-select.scss
index 439fcbe..c10a8b5 100644
--- a/sass/bootstrap-select.scss
+++ b/sass/bootstrap-select.scss
@@ -696,7 +696,7 @@ select.selectpicker {
border-bottom: 1px solid var(--bs-border-color-translucent, rgba(0, 0, 0, 0.1));
& + .bs-actionsbox {
- padding: 0 8px 4px;
+ padding: 0.5rem 0.75rem 0.25rem;
}
& .form-control {
diff --git a/tests/e2e/single-select.spec.js b/tests/e2e/single-select.spec.js
index 02e1a5f..7416b66 100644
--- a/tests/e2e/single-select.spec.js
+++ b/tests/e2e/single-select.spec.js
@@ -210,6 +210,70 @@ test('menu header renders its close button at the end with compact spacing', asy
});
});
+test('dropupAuto keeps the menu below when there is room for controls and one option', async ({ page }) => {
+ await page.setViewportSize({ width: 900, height: 520 });
+ await page.goto('/tests/index.html');
+ await page.waitForFunction(() => window.Selectpicker);
+
+ await page.evaluate(() => {
+ document.body.innerHTML += `
+